【发布时间】:2018-07-17 07:14:40
【问题描述】:
我有一个自定义的可等待类型,问题是继续在不同的线程上恢复,这会导致 WinForms/WPF/MVC/etc 等 UI 出现问题:
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 实现不太正确。
我试图深入研究Task.ConfigureAwait(bool).GetAwaiter() 方法返回的ConfiguredTaskAwaiter,可以看到黑魔法发生在SynchronizationContextAwaitTaskContinuation 类中,但这是一个内部类,以及许多其他内部使用的类型。有没有办法重构我的 OnCompleted 实现以按预期工作?
更新:反对者注意:我知道我在OnCompleted 做了不正当的事情,这就是我问的原因。如果您对质量(或其他任何事情)有疑问,请发表评论并帮助我改进问题,以便我也可以帮助您更好地突出问题。谢谢。
注意 2:我知道我可以使用TaskCompletionSource<TResult> 及其常规Task<TResult> 结果的解决方法,但我想了解背景。 这是唯一的动力。纯粹的好奇心。
更新 2:我调查的重要参考资料:
等待者的工作原理:
一些实现:
【问题讨论】:
-
您需要在运行 awaitable 之前捕获同步上下文。
-
即使我在构造函数中捕获它,结果也是一样的。
-
为什么要使用 Task 构造函数,为什么要在其中使用这些参数?
-
@PauloMorgado:
continuation是我要执行的委托。LongRunning选项是对TaskScheduler的提示,表示执行可以持续很长时间。默认调度程序实现为此类任务创建一个新的Thread,而不是使用ThreadPool(但实际上可以忽略)。为什么 ctor:我猜你在问为什么不Task.Run或Task.Factory.StartNew。这是因为我不需要等到Task被安排好并且它的状态在启动后变为正在运行。 -
@taffer 好的。我会删除我之前的评论,以免引起负面评论。
标签: c# async-await