Task continuation on UI thread

109,928

Solution 1

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.

Solution 2

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.

Solution 3

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

Solution 4

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

Solution 5

Got here through google because i was looking for a good way to do things on the ui thread after being inside a Task.Run call - Using the following code you can use await to get back to the UI Thread again.

I hope this helps someone.

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

Usage:

... code which is executed on the background thread...
await UI.Thread;
... code which will be run in the application dispatcher (ui thread) ...

Share:
109,928

Related videos on Youtube

Greg Sansom
Author by

Greg Sansom

Updated on May 19, 2020

Comments

  • Greg Sansom
    Greg Sansom over 3 years

    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"; 
        }
    });
    
    • Colonel Panic
      Colonel Panic over 7 years
      In the case of your example, you could use Control.Invoke(Action), ie. TextBlock1.Invoke rather than dispatcher.Invoke
    • Greg Sansom
      Greg Sansom over 7 years
      Thanks @ColonelPanic, but I was using WPF (as tagged), not winforms.
  • stukselbax
    stukselbax over 9 years
    Its valid only if the current execution context is on the UI thread. If you put this code inside another Task, then you get InvalidOperationException (look at Exceptions section)
  • Kirill Shlenskiy
    Kirill Shlenskiy about 9 years
    Not downvoting since this is a viable solution in some scenarios; however, the accepted answer is way better. It is technology-agnostic (TaskScheduler is part of BCL, Dispatcher is not) and can be used to compose complex chains of tasks due to not having to worry about any fire-and-forget async operations (such as BeginInvoke).
  • Dean
    Dean about 9 years
    @Kirill can you expand a little, because some SO threads have unanimously declared the dispatcher to be the correct method if using WPF of WinForms: One can invoke a GUI update either asynchronously (using BeginInvoke) or synchronously (Invoke), though typically the async is used because one would not want to block a background thread just for a GUI update. Does FromCurrentSynchronizationContext not put the continuation task into the main thread message queue in just the same way as the dispatcher?
  • Kirill Shlenskiy
    Kirill Shlenskiy about 9 years
    sure thing. I'll address the Dispatcher issue first. Dispatcher.Invoke/BeginInvoke is a WPF concept; its counterpart in WinForms would be Control.Invoke/BeginInvoke. So now you have to tailor your code to the specific platform you're working with - which wouldn't be the case if you went with TaskScheduler.FromCurrentSynchronizationContext in the first place as it is part of the Base Class Library, and is therefore widely available.
  • Dean
    Dean about 9 years
    Right, but the OP is certainly asking about WPF (and tagged it so), and does not want to keep a reference to any dispatcher (and I assume any synchronization context either - you can only get this from the main thread and you have to store a reference to it somewhere). Which is why I like the solution that I posted: there is a thread-safe static reference built in that requires none of this. I think this is extremely useful in the WPF context.
  • Dean
    Dean about 9 years
    Just wanted to reinforce my last comment: The developer not only has to store the sync context, but he/she has to know that this is only available from the main thread; this problem has been the cause of confusion in dozens of SO questions: People all the time try to get that from the the worker thread. If their code has itself been moved into a worker thread, it fails because of this issue. So because of the prevalence of WPF, this should definitely be clarified here in this popular question.
  • Kirill Shlenskiy
    Kirill Shlenskiy about 9 years
    The portable solution is just as simple as the non-portable one (some might argue that passing around a TaskScheduler reference is a chore - others would rebut that it is not always necessary, and in simple scenarios there is actually less typing involved), so it wins every time simply because you get more bang for your buck.
  • Dean
    Dean about 9 years
    Well, the accepted answer is more than double the amount of code: You have to plug it into the code from the question.
  • Sebastien F.
    Sebastien F. about 8 years
    I'm guessing the = before GenerateManifest is a typo.
  • Marcel Wolterbeek
    Marcel Wolterbeek over 7 years
    In .NET 4.5 Johan Larsson's answer should be used as standard way for a task continuation on the UI thread. Just write: await Task.Run(DoLongRunningWork); this.TextBlock1.Text = "Complete"; See also: blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
  • ToolmakerSteve
    ToolmakerSteve over 5 years
    The comment under the false version confuses me. I thought false means it might continue on a different thread.
  • ToolmakerSteve
    ToolmakerSteve over 5 years
    FWIW, The accepted answer would be 5 characters shorter than this answer, and typically be formatted to the same 6 lines as this, if didn't declare the intermediate task: Task.Factory.StartNew( () => { DoLongRunningWork(); } ).ContinueWith( () => { this.TextBlock1.Text = "Complete"; }, TaskScheduler.FromCurrentSynchronizationContext() );
  • ToolmakerSteve
    ToolmakerSteve over 5 years
    ... nevertheless, Dean's observation about [the accepted answer] needing to keep track of sync context if code might not be on main thread is important to note, and avoiding that is a benefit of this answer.
  • Max Barraclough
    Max Barraclough almost 5 years
    @ToolmakerSteve Depends which thread you're thinking of. The worker thread used by Task.Run, or the caller thread? Remember, "same thread the task finished on" means the worker thread (avoiding 'switching' between threads). Also, ConfigureAwait(true) doesn't guarantee that control returns to the same thread, only to the same context (though the distinction may not be significant).
  • ToolmakerSteve
    ToolmakerSteve almost 5 years
    @MaxBarraclough - Thanks, I misread which "same thread" was meant. avoiding switching between threads in the sense of maximizing performance by using whatever thread happens to be running [to perform the "do some stuff" task], that clarifies it for me.
  • user8276908
    user8276908 over 4 years
    I was just about to down vote this also but based on the observation discussed here I will leave it hoping that others will up-vote the accepted answer way more than this one - both answers address the issue of the question but the accepted answer is just way better in terms of re-usability and maintainance...
  • C-H-a-P
    C-H-a-P about 4 years
    Thx for saving my life. I spend hours to figure out how to call main thread things within the await/ContinueWith. For everyone else how is using Google Firebase SDK for Unity and still has the same issues, this is a working approach.
  • ToolmakerSteve
    ToolmakerSteve almost 4 years
    The question doesn't specify being inside of an async method (which is necessary, to use await). What is the answer when await is not available?
  • ToolmakerSteve
    ToolmakerSteve almost 4 years
    @MarcelW - await is a good pattern - but only if you are inside an async context (such as a method declared async). If not, it is still necessary to do something like this answer.
  • Theodor Zoulias
    Theodor Zoulias over 3 years
    Very clever! Quite unintuitive though. I suggest making static the class UI.
  • Jon Bangsberg
    Jon Bangsberg almost 2 years
    @MarcelW Your link is dead and there's no cache of this page on the Wayback Machine. Can you find another source?
  • Marcel Wolterbeek
    Marcel Wolterbeek almost 2 years
    @JonBangsberg You can look at the ConfigureAwait FAQ by Stephen Toub. There is a lot of interesting information about async.
  • maxp
    maxp almost 2 years
    If this code executes 'off' the main thread, TaskScheduler.FromCurrentSynchronizationContext() will throw an exception. Does this not seem a bit flakey?