2
4

2 回答 2

3

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
}
于 2014-11-21T08:52:47.703 回答
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.

于 2014-11-21T08:30:47.580 回答