Task continuation on UI thread

2019-01-04 16:31发布

Is there a 'standard' way to specify that a task continuation should run on the thread from which the initial task was created?

Currently I have the code below - it is working but keeping track of the dispatcher and creating a second Action seems like unnecessary overhead.

dispatcher = Dispatcher.CurrentDispatcher;
Task task = Task.Factory.StartNew(() =>
{
    DoLongRunningWork();
});

Task UITask= task.ContinueWith(() =>
{
    dispatcher.Invoke(new Action(() =>
    {
        this.TextBlock1.Text = "Complete"; 
    }
});

5条回答
戒情不戒烟
2楼-- · 2019-01-04 17:03

I just wanted to add this version because this is such a useful thread and I think this is a very simple implementation. I have used this multiple times in various types if multithreaded application:

 Task.Factory.StartNew(() =>
      {
        DoLongRunningWork();
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
              { txt.Text = "Complete"; }));
      });
查看更多
可以哭但决不认输i
3楼-- · 2019-01-04 17:06

Call the continuation with TaskScheduler.FromCurrentSynchronizationContext():

    Task UITask= task.ContinueWith(() =>
    {
     this.TextBlock1.Text = "Complete"; 
    }, TaskScheduler.FromCurrentSynchronizationContext());

This is suitable only if the current execution context is on the UI thread.

查看更多
淡お忘
4楼-- · 2019-01-04 17:11

With async you just do:

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);

However:

await Task.Run(() => do some stuff).ConfigureAwait(false);
// continue doing stuff on the same thread as the task finished on.
查看更多
孤傲高冷的网名
5楼-- · 2019-01-04 17:12

If you have a return value you need to send to the UI you can use the generic version like this:

This is being called from an MVVM ViewModel in my case.

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());
查看更多
淡お忘
6楼-- · 2019-01-04 17:16

Just write your code as(But using ContinueWith is a good practice, don't worry about unnecessary overhead for runtime )

 Task task = Task.Factory.StartNew(() =>
{
    DoLongRunningWork();
    Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
    {
        this.TextBlock1.Text = "Complete"; 
    }
});

Put Dispatcher code in finally block if you want to make sure to this to run.

Try Avoiding TaskScheduler.FromCurrentSynchronizationContext() as from using this your UI Thread can be blocked by your current Thread.

查看更多
登录 后发表回答