2 回答
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
}
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.