Продолжение задачи в потоке пользовательского интерфейса
Существует ли "стандартный" способ указать, что продолжение задачи должно выполняться в потоке, из которого была создана первоначальная задача?
В настоящее время у меня есть код ниже - он работает, но отслеживание диспетчера и создание второго действия кажется ненужными накладными расходами.
dispatcher = Dispatcher.CurrentDispatcher;
Task task = Task.Factory.StartNew(() =>
{
DoLongRunningWork();
});
Task UITask= task.ContinueWith(() =>
{
dispatcher.Invoke(new Action(() =>
{
this.TextBlock1.Text = "Complete";
}
});
4 ответа
Назовите продолжение с TaskScheduler.FromCurrentSynchronizationContext()
:
Task UITask= task.ContinueWith(() =>
{
this.TextBlock1.Text = "Complete";
}, TaskScheduler.FromCurrentSynchronizationContext());
Это подходит, только если текущий контекст выполнения находится в потоке пользовательского интерфейса.
С помощью async вы просто делаете:
await Task.Run(() => do some stuff);
// continue doing stuff on the same context as before.
// while it is the default it is nice to be explicit about it with:
await Task.Run(() => do some stuff).ConfigureAwait(true);
Тем не мение:
await Task.Run(() => do some stuff).ConfigureAwait(false);
// continue doing stuff on the same thread as the task finished on.
Если у вас есть возвращаемое значение, которое вам нужно отправить в пользовательский интерфейс, вы можете использовать общую версию, например:
В моем случае это вызывается из MVVM ViewModel.
var updateManifest = Task<ShippingManifest>.Run(() =>
{
Thread.Sleep(5000); // prove it's really working!
// GenerateManifest calls service and returns 'ShippingManifest' object
return GenerateManifest();
})
.ContinueWith(manifest =>
{
// MVVM property
this.ShippingManifest = manifest.Result;
// or if you are not using MVVM...
// txtShippingManifest.Text = manifest.Result.ToString();
System.Diagnostics.Debug.WriteLine("UI manifest updated - " + DateTime.Now);
}, TaskScheduler.FromCurrentSynchronizationContext());
Я просто хотел добавить эту версию, потому что это такая полезная тема, и я думаю, что это очень простая реализация. Я использовал это несколько раз в различных типах многопоточных приложений:
Task.Factory.StartNew(() =>
{
DoLongRunningWork();
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{ txt.Text = "Complete"; }));
});
Попал сюда через Google, потому что я искал хороший способ делать что-то в потоке пользовательского интерфейса после того, как был внутри вызова Task.Run. Используя следующий код, вы можете использовать await
чтобы снова вернуться к потоку пользовательского интерфейса.
Я надеюсь, что это помогает кому-то.
public static class UI
{
public static DispatcherAwaiter Thread => new DispatcherAwaiter();
}
public struct DispatcherAwaiter : INotifyCompletion
{
public bool IsCompleted => Application.Current.Dispatcher.CheckAccess();
public void OnCompleted(Action continuation) => Application.Current.Dispatcher.Invoke(continuation);
public void GetResult() { }
public DispatcherAwaiter GetAwaiter()
{
return this;
}
}
Применение:
... code which is executed on the background thread...
await UI.Thread;
... code which will be run in the application dispatcher (ui thread) ...
Просто напишите свой код как (Но используяContinueWith
это хорошая практика, не беспокойтесь о ненужных накладных расходах во время выполнения)
Task task = Task.Factory.StartNew(() =>
{
DoLongRunningWork();
Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
{
this.TextBlock1.Text = "Complete";
}
});
Положил
Dispatcher
код вfinally
заблокировать, если вы хотите убедиться, что это работает.
Попробуйте избежать TaskScheduler.FromCurrentSynchronizationContext()
как с помощью этого вашего интерфейса Thread
может быть заблокирован вашим текущим Thread
,