C# 未配置的等待等待(false)在另一个线程上继续
我有一个WinForms应用程序,我有一些需要在UI线程上运行的代码。但是,C# 未配置的等待等待(false)在另一个线程上继续,c#,.net,winforms,async-await,C#,.net,Winforms,Async Await,我有一个WinForms应用程序,我有一些需要在UI线程上运行的代码。但是,wait之后的代码在不同的线程上运行 protected override async void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); // This runs on the UI thread. mainContainer.Controls.Clear(); var result = await DoSomet
wait
之后的代码在不同的线程上运行
protected override async void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
// This runs on the UI thread.
mainContainer.Controls.Clear();
var result = await DoSomethingAsync();
// This also needs to run on the UI thread, but it does not.
// Instead it throws an exception:
// "Cross-thread operation not valid: Control 'mainContainer' accessed from a thread other than the thread it was created on"
mainContainer.Controls.Add(new Control());
}
我还尝试显式添加ConfigureAwait(true)
,但没有任何区别。我的理解是,如果省略了ConfigureAwait(false)
,那么继续应该在原始线程上运行。在某些情况下这是不正确的吗
我还注意到,如果我在wait之前向集合添加一个控件,那么continuation会神奇地在正确的线程上运行
protected override async void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
// This runs on the UI thread.
mainContainer.Controls.Add(new Control());
mainContainer.Controls.Clear();
var result = await DoSomethingAsync();
// This also runs on the UI thread now. Why?
mainContainer.Controls.Add(new Control());
}
我的问题是:
DoSomethingAsync
的重要部分供参考。它使用RestSharp提交HTTP请求
protected async Task DoSomethingAsync()
{
IRestRequest request = CreateRestRequest();
// Here I await the response from RestSharp.
// Client is an IRestClient instance.
// I have tried removing the ConfigureAwait(false) part, but it makes no difference.
var response = await Client.ExecuteTaskAsync(request).ConfigureAwait(false);
if (response.ResponseStatus == ResponseStatus.Error)
throw new Exception(response.ErrorMessage ?? "The request did not complete successfully.");
if (response.StatusCode >= HttpStatusCode.BadRequest)
throw new Exception("Server responded with an error: " + response.StatusCode);
// I also do some processing of the response here; omitted for brevity.
// There are no more awaits.
}
似乎在OnHandleCreated
上发生了一些奇怪的事情。我的解决方案是使用OnLoad
。我对这个解决方案非常满意,因为在我的情况下没有理由使用OnHandleCreated
我仍然很好奇为什么会发生这种情况,所以如果有人知道,请随意发布另一个答案
编辑:
我发现了真正的问题:原来我是在ConfigureAwait(false)
之后调用Form.ShowDialog()
。因此,表单是在UI线程上构建的,但随后我在非UI线程上调用了ShowDialog
。我很惊讶这居然奏效了
我已经删除了ConfigureAwait(false)
,因此现在在UI线程上调用了ShowDialog
我的理解是,如果省略ConfigureAwait(false),那么continuation应该在原始线程上运行。在某些情况下这是不正确的吗
实际上,默认情况下,await
将捕获当前上下文,并使用此上下文恢复async
方法。此上下文是SynchronizationContext.Current
,除非它是null
,在这种情况下,它是TaskScheduler.Current
(通常是线程池上下文)。大多数时候,UI线程都有一个UISynchronizationContext
——对于WinForms,它是WinFormsSynchronizationContext
的一个实例
我还注意到,如果我在wait之前向集合添加一个控件,那么continuation会神奇地在正确的线程上运行
protected override async void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
// This runs on the UI thread.
mainContainer.Controls.Add(new Control());
mainContainer.Controls.Clear();
var result = await DoSomethingAsync();
// This also runs on the UI thread now. Why?
mainContainer.Controls.Add(new Control());
}
没有线程自动以同步上下文开始。WinFormsSynchronizationContext
在创建第一个控件时按需安装。这就是为什么在创建控件后,它会在UI线程上恢复
由于移动到
OnLoad
是一个可行的解决方案,我建议您还是这样做吧。唯一的其他选项(在创建控件之前在UI线程上恢复)是在第一次wait
之前手动创建控件。为什么您认为这会在不同的线程上运行?这段代码在哪里运行?事件处理程序?我知道它在另一个线程上运行,因为等待后的代码引发异常:“跨线程操作无效:从创建它的线程以外的线程访问控件“mainContainer”。是的,代码在事件处理程序中运行。我将更新我的问题。OnHandleCreated
注册到哪个事件?因为winforms中的事件处理程序通常有两个参数。HandleCreated
事件肯定涉及到黑魔法。我切换到使用Load
事件,一切正常。无论如何,我不知道为什么要使用HandleCreated
。(我从其他人那里继承了这段代码,很多代码都很凌乱)。你能把Debug.WriteLine(new{SynchronizationContext.Current})
放在await DoSomethingAsync()
前面的OnHandleCreated
中吗?它是否显示System.Windows.Forms.WindowsFormsSynchronizationContext
(预期)或System.Threading.SynchronizationContext
?我在等待之前得到System.Threading.SynchronizationContext
,在等待之后得到null。我不确定昨天为什么会得到一个非空值。但我想这就是原因。我还注意到,如果我在repo的不同分支上的同一项目中运行相同的代码,我会得到一个System.Windows.Forms.windowsformsssynchronizationcontext
,并且一切正常。我想知道这是否与表单初始化的方式有关。在您最近的编辑中,您应该将您的答案标记为已接受,因为它解释了实际发生的情况。否则可以在OnHandleCreated
中使用wait
,查看我对Stephen的回答。@Noseratio是的,我必须等待两天才能接受我自己的回答。我会接受它一次,所以让我:)实际上,WindowsFormsSynchronizationContext
安装在Control
的构造函数中,所以调用OnHandleCreated
时它应该已经存在了。这肯定是另外一回事。这可能是相关的,但在这种情况下,OnHandleCreated
和OnLoad
都会受到影响。