如何正确实现自定义等待者的OnCompleted方法? [英] How to implement the OnCompleted method of a custom awaiter correctly?
问题描述
我有一个自定义的等待类型,问题是继续在不同的线程上继续进行,这会在WinForms/WPF/MVC/etc等UI中引起问题:
I have a custom awaitable type and the problem is that the continuation resumes on a different thread, which causes problems in UIs such as WinForms/WPF/MVC/etc:
private MyAwaitable awaitable;
private async void buttonStart_Click(object sender, EventArgs e)
{
awaitable = new MyAwaitable(false);
progressBar1.Visible = true;
// A regular Task can marshal the execution back to the UI thread
// Here ConfigureAwait is not available and I don't know how to control the flow
var result = await awaitable;
// As a result, here comes the usual "Cross-thread operation not valid" exception
// A [Begin]Invoke could help but regular Tasks also can handle this situation
progressBar1.Visible = false;
}
private void buttonStop_Click(object sender, EventArgs e) => awaitable.Finish();
这是 MyAwaitable
类:
public class MyAwaitable
{
private volatile bool finished;
public bool IsFinished => finished;
public MyAwaitable(bool finished) => this.finished = finished;
public void Finish() => finished = true;
public MyAwaiter GetAwaiter() => new MyAwaiter(this);
}
有问题的自定义侍者:
public class MyAwaiter : INotifyCompletion
{
private readonly MyAwaitable awaitable;
private readonly SynchronizationContext capturedContext = SynchronizationContext.Current;
public MyAwaiter(MyAwaitable awaitable) => this.awaitable = awaitable;
public bool IsCompleted => awaitable.IsFinished;
public int GetResult()
{
var wait = new SpinWait();
while (!awaitable.IsFinished)
wait.SpinOnce();
return new Random().Next();
}
public void OnCompleted(Action continuation)
{
// continuation(); // This would block the UI thread
// Task constructor + Start was suggested by the references I saw,
// Results with Task.Run/Task.Factory.StartNew are similar.
var task = new Task(continuation, TaskCreationOptions.LongRunning);
// If executed from a WinForms app, we have a WinFormsSyncContext here,
// which is promising, still, it does not solve the problem.
if (capturedContext != null)
capturedContext.Post(state => task.Start(), null);
else
task.Start();
}
}
我怀疑我的 OnCompleted
实现不太正确.
I suspect that my OnCompleted
implementation is not quite correct.
我试图深入研究 Task.ConfigureAwait(bool).GetAwaiter()
方法返回的 ConfiguredTaskAwaiter
,并且可以看到黑魔法发生在> SynchronizationContextAwaitTaskContinuation
类,但这是一个内部类,还有许多其他内部使用的类型.有没有办法重构我的 OnCompleted
实现以按预期工作?
I tried to dig into the ConfiguredTaskAwaiter
returned by the Task.ConfigureAwait(bool).GetAwaiter()
method and could see that the black magic happens in a SynchronizationContextAwaitTaskContinuation
class but that is an internal one, along with lot of other internally used types. Is there a way to refactor my OnCompleted
implementation to work as expected?
更新:反对者的注记:我知道我在 OnCompleted
中做不正确的事情,这就是为什么要问这个问题.如果您对质量(或其他任何方面)有疑问,请发表评论并帮助我改善问题,以便我也可以帮助您更好地突出问题.谢谢.
Update: Note to downvoters: I know I do improper things in OnCompleted
, that's why I ask. If you have concerns about quality (or anything else) please leave a comment and help me to improve the question so I also can help you to highlight the problem better. Thanks.
注释2 :我知道我可以对 TaskCompletionSource< TResult>
及其常规 Task< TResult>
结果使用变通办法,但是我想了解背景.这是唯一的动机.纯粹的好奇心.
Note 2: I know I could use a workaround with a TaskCompletionSource<TResult>
and its regular Task<TResult>
result but I would like to understand the background. This is the only motivation. Pure curiosity.
更新2 :我调查过的重要参考文献:
Update 2: Notable references I investigated:
服务员的工作方式:
一些实现:
- https://marcinotorowski.com/2018/03/13/tap-await-anything-not-only-tasks/-错误,阻止调用者线程,直到
GetResult
返回 - https://blogs.msdn.microsoft.com/pfxteam/2011/01/13/await-anything -只是呼叫其他服务员
- https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-2-awaitable-awaiter-pattern -与我的相似,存在相同的问题
- https://marcinotorowski.com/2018/03/13/tap-await-anything-not-only-tasks/ - wrong, blocks caller thread until
GetResult
returns - https://blogs.msdn.microsoft.com/pfxteam/2011/01/13/await-anything - just calls other awaiters
- https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-2-awaitable-awaiter-pattern - similar to mine, has the same issue