2

Let's say I have a Form that tries to deal with a multi-threaded environment; it therefore checks if it's running on the UI thread before any UI modification is done:

partial class SomeForm : Form
{
    public void DoSomethingToUserInterface()
    {
        if (InvokeRequired)
        {
            BeginInvoke(delegate { DoSomethingToUserInterface() });
        }
        else
        {
            … // do the actual work (e.g. manipulate the form or its elements)
        }
    }
}

Now let's say I am performing some lengthy operation inside the part of that method; I'd therefore like to make it asynchronous using async/await.

Given that I should change the method signature to return a Task instead of void (so that exceptions can be caught), how would I implement the part that performs the BeginInvoke? What should it return?

public async Task DoSomethingToUserInterfaceAsync()
{
    if (InvokeRequired)
    {
        // what do I put here?
    }
    {
        … // like before (but can now use `await` expressions)
    }
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
  • Btw., I am considering extracting the lengthy operation and, once completed, pass its result as an argument to `DoSomethingToUserInterface`, which no longer needs to be `async` at all (thereby avoiding the problem presented here). I'm still interested in a solution, though. – stakx - no longer contributing Nov 21 '14 at 08:30
  • 4
    In general, `async`/`await` should obviate the need for `Invoke()`. I.e. where you used to have a situation in which you were calling these methods from a potentially off-GUI-thread context, you now should be able to configure your code to avoid that altogether. So rather than asking how to change the method signature here, you should be asking how to change the caller to avoid the need for invoking in the first place. – Peter Duniho Nov 21 '14 at 08:32
  • @PeterDuniho: That appears to be a reasonable suggestion. So I basically have to trace back the chain of callers that led to `DoSomethingToUserInterfaceAsync` (up to some method that is likely a Windows Forms UI event handler), make all these caller methods use `async/await`, too? – stakx - no longer contributing Nov 21 '14 at 08:39
  • 1
    @stakx why this question? `await` ensures that the code after the asynchronous operation will run on the original thread. If that thread is the UI thread, the code will run on the UI thread and you won't need BeginInvoke at all. Does the caller of your `async` method use `Task.Run` ? – Panagiotis Kanavos Nov 21 '14 at 08:46
  • @PanagiotisKanavos: I am asking because I am only now learning all this, and don't yet know what might be obvious to more experienced fellows, such as you. – Your comment is helpful. I think I understand now [what Peter Duino suggested above](http://stackoverflow.com/questions/27056972/how-do-i-implement-if-invokerequired-begininvokethismethodasync-inside-an#comment42629562_27056972). Thanks! – stakx - no longer contributing Nov 21 '14 at 08:49
  • Please post the code that calls `DoSomethingToUserInterface` otherwise the question doesn't make much sense. Unless you try to update the UI from inside a Task, Invoke should never be required – Panagiotis Kanavos Nov 21 '14 at 08:58
  • @stakx If you're new to async await, I suggest you to read [stephen cleary's blog](http://blog.stephencleary.com/2012/02/async-and-await.html) there are several posts about async-await. Also I recommend his book. – Sriram Sakthivel Nov 21 '14 at 09:08
  • @PanagiotisKanavos: This question is general in nature, about a well-known Windows Forms pattern and how to deal with it in circumstances that could not have existed when it was invented. The code I have shown is therefore intentionally generic; there is no concrete calling code that I could show. In fact, the pattern's raison d'être is exactly that it *should not matter* where the method was called from. I would therefore claim that the question makes sense even without seeing the calling code. – stakx - no longer contributing Nov 21 '14 at 09:27
  • @PanagiotisKanavos: That being said, I have come to understand, thanks to previous comments, that looking at the calling method(s) instead of looking at just the called method itself offers ways to completely avoid this situation. – stakx - no longer contributing Nov 21 '14 at 09:30

2 Answers2

4

When using async-await with a custom awaiter such as the common TaskAwaiter, the SynchronizationContext is being implicitly captured for you. Later, when the asynchronous method completes, the continuation (any code after the await) is being marshaled back to that same sync context using SynchronizationContext.Post.

This altogether eliminates the need to use InvokeRequired and other techniques used to mainpulate work on the UI thread. In order to do that, you'll have to trace your method calls all the way to the top level ones and refactor them to use async-await probably.

But, to address the specific problem as is, what you can do is capture the WinFormSynchronizationContext when your Form initializes:

partial class SomeForm : Form
{
    private TaskScheduler _uiTaskScheduler;
    public SomeForm()
    {
        _uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
    }
}

And later use it when you want to await:

if (InvokeRequired)
{
    Task uiTask = new Task(() => DoSomethingToUserInterface());
    uiTask.RunSynchronously(_uiTaskScheduler);
}
else
{
    // Do async work
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • 1
    First, .NET already does that for you. There's a bug in the rest of the code if Invoke is really required. Second, SynchronizationContext.Post is a simpler way to run a delegate on the context without involving Tasks at all – Panagiotis Kanavos Nov 21 '14 at 08:56
  • @PanagiotisKanavos You can't `await` the call to `SynchronizationContext.Post`. You claim latter is better? I don't think so. – Sriram Sakthivel Nov 21 '14 at 08:58
  • About the second part, you're right. `RunSynchronously` is merely a wrapper around `SyncContext.Post`. About the first part, why is there a bug in the code if `Invoke` is required? – Yuval Itzchakov Nov 21 '14 at 08:58
  • @SriramSakthivel I'm not sure the OP wants to `await` the body statement inside `InvokeRequired`. – Yuval Itzchakov Nov 21 '14 at 08:59
  • @YuvalItzchakov If he wants he can do with tasks. My point is `SynchronizationContext.Post` isn't any better than TPL as PanagiotisKanavos seems to be claiming. It is very good option IFF you're constrained to .net 3.5 or lesser. – Sriram Sakthivel Nov 21 '14 at 09:01
  • @SriramSakthivel You're right. Using a `Task` is simply a wrapper around the operation and it doesn't make much difference at all in the way to OP currently uses his code. – Yuval Itzchakov Nov 21 '14 at 09:02
  • @YuvalItzchakov it wastes CPU, creates unnecessary code and objects and hides what's going on. The point though is that there is a bug in the code; involving the SyncrhonizatinoContext simply hides the bug behind code, it doesn't fix it. – Panagiotis Kanavos Nov 21 '14 at 09:04
  • 1
    @PanagiotisKanavos It wastes CPU? If your program is that sensitive to memory allocations then you have a different problem. the OP doesn't state anything about that at all. It doesn't hide anything, as you're explicitly passing a delegate to the `Task` being executed. It also doesn't hide a *bug*, it hides misunderstanding of the `async-await` pattern, which both Sriram and I stated should be the actual pattern being used. – Yuval Itzchakov Nov 21 '14 at 09:06
1

You can use TaskScheduler.FromCurrentSynchronizationContext to get the task scheduler for current synchronization context(For UI thread), and store it in a field for later use.

Then when you're interested to start any task in UI thread, you have to pass the uiScheduler to the StartNew method, so that TPL will schedule the task in the provided scheduler(UI thread in this case).

Anyway you decided to run the stuff in UI thread, so just schedule it to UIScheduler, you don't need to check for InvokeRequired whatsoever.

public async Task DoSomethingToUserInterfaceAsync()
{
    await Task.Factory.StartNew(() => DoSomethingToUserInterface(), CancellationToken.None, TaskCreationOptions.None, uiScheduler);
    ...
}

To retrieve the UI scheduler, you can use the following code

private TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

Note: It is extremely important that TaskScheduler.FromCurrentSynchronizationContext should be called only from UI thread, otherwise, it will throw exception, or you'll get a TaskScheduler for some other SynchronizationContext which won't do what you need.

Also note that if you have started the async operation from UI thread itself, you don't need any of the above magic. await will resume in the context where it has been started.

Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
  • `FromCurrentSynchronizationContext` will only give you the `WinFormsSynchronizationContext` if the method call was executing on the UI thread. If `BeginInvoke` is required (hence true), he isn't on the UI thread and will most probably get the default `ThreadPoolSynchronizationContext`. – Yuval Itzchakov Nov 21 '14 at 08:33
  • @YuvalItzchakov read my first line of the answer. *You have to store the UI task scheduler* – Sriram Sakthivel Nov 21 '14 at 08:34
  • Right. That is probably misleading though as it doesn't explain much. Maybe you should elaborate more on the fact that the sync context needs to be stored while running on the UI thread. – Yuval Itzchakov Nov 21 '14 at 08:35
  • @Sriram: How would I do that? (Could you please show an example?) – stakx - no longer contributing Nov 21 '14 at 08:36
  • @stakx I updated my answer, drop me a comment if anything isn't clear or you need more help. – Sriram Sakthivel Nov 21 '14 at 08:45
  • It would really help if @downvoter drop some constructive comment. – Sriram Sakthivel Nov 21 '14 at 08:49
  • `await` ensures code will run on the original thread. If the OP is trying to update the UI from inside a Task, there is a bug in the rest of the form code. Passing the synchronization context around simply complicates the issue. If you really need to work with the context, you should use [SynchronizationContext.Post](http://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext.post%28v=vs.110%29.aspx) instead of using a ThreadPool thread to do the same thing – Panagiotis Kanavos Nov 21 '14 at 08:53
  • @PanagiotisKanavos This answer assumes that OP starts the operation from elsewhere and not from UI thread. Not sure where we are using `ThreadPool` here(in my answer)? And how does `SynchronizationContext.Post` differs from scheduling the task in UIScheduler? – Sriram Sakthivel Nov 21 '14 at 08:57