使用 Control.BeginInvoke() 调用异步方法时如何防止警告 VSTHRD101?
How to prevent warning VSTHRD101 when using Control.BeginInvoke() to call an async method?
我正在重构一些旧的 WinForms 代码以使用 async/await,我遇到了代码分析警告,我不确定如何正确修复。
情况是我有一个从非UI线程调用的方法需要更新UI。旧代码使用历史悠久的技术调用 Control.BeginInvoke()
到 运行 UI 线程上的代码。
问题是我现在要调用的方法是 async
并且尝试使用 Control.BeginInvoke()
会产生警告 VSTHRD101
:“避免将异步 lambda 用于返回无效的委托类型,因为任何未被委托处理的异常都会导致进程崩溃。"
以下示例 WinForms 应用程序演示了该问题。这是一个简单的表单,仅包含一个名为“testBtn”的 Button
和一个名为“label1”的 Label
。
在 notRunningOnUiThread()
方法中调用 ControlBeginInvoke()
会导致警告,如评论所述:
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (disabled for brevity in this sample code)
namespace Net5WinFormsApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = true; // For test purposes to prove that mustRunOnUiThreadAsync() runs on the UI thread.
}
// This method is only to simulate what's happening in the real code.
// Assume that you cannot change this.
void testBtn_Click(object sender, EventArgs e)
{
_ = Task.Run(() => notRunningOnUiThread(this)); // Not my real code, but this simulates it.
}
// NOTE: In the real code this method is not a member of the Control,
// but it does have access to the Control to update.
void notRunningOnUiThread(Control ui) // Called from a non-UI thread and wants to call a method that updates the UI.
{
// The next line causes warning VSTHRD101:
// "Avoid using async lambda for a void returning delegate type,
// because any exceptions not handled by the delegate will crash the process."
ui.BeginInvoke(new Action(async () => await mustRunOnUiThreadAsync()));
}
async Task mustRunOnUiThreadAsync()
{
label1.Text = "Before update";
await Task.Delay(1000).ConfigureAwait(true);
label1.Text = "After update";
}
}
}
如果我抑制警告,即使 mustRunOnUiThreadAsync()
中有异常,一切似乎都 运行 正常(如果有异常,它会立即报告,而不是来自任务的终结器)。
我的问题是: 禁止显示此警告是否安全?如果不是,有什么好的修复方法?
我要解决的根本问题是如何从非[=64]线程安全地调用UI线程上的异步UI方法=] WinForms 应用程序中的线程,当只有 ui Form
可用且当前同步上下文不可用时。
(请注意,我不能在 notRunningOnUiThread()
中简单地 await mustRunOnUiThreadAsync()
。那会导致抛出“跨线程操作无效”InvalidOperationException
。)
我想一个可能的解决方案是使用 the advice from this answer 编写调用异步方法的非异步方法。但这是针对这种特殊情况的最佳方法吗?
[编辑]
我应该提到:在我的实际代码中,notRunningOnUiThread()
不是需要更新 UI 的 Control
的成员。因为 notRunningOnUiThread()
不是 运行 在 UI 线程上,所以我不能使用 System.Threading.SynchronizationContext
,因为它是错误的上下文。但是,我确实有 Control
可用于调用 Control.BeginInvoke()
。
实际情况是我正在为一个 RPC 请求提供服务,该请求必须使用恰好是异步的方法更新一些 UI。
一个想法可能是将 mustRunOnUiThreadAsync
从 async Task
转换为 async void
。从概念上讲,您可以说此方法是源自后台线程的“事件”的“事件处理程序”,因此您基本上没有违反 Microsoft's guidelines 关于 async void
的用法。示例:
async void mustRunOnUiThread()
{
label1.Text = "Before update";
await Task.Delay(1000).ConfigureAwait(true);
label1.Text = "After update";
}
...并且在 notRunningOnUiThread
方法中:
ui.BeginInvoke(new Action(() => mustRunOnUiThread()));
此修改不会以任何方式改变您当前代码的行为,除了使 VSTHRD101 警告消失。
作为旁注,您还可以考虑将 testBtn_Click
处理程序转换为 async void
,这样 notRunningOnUiThread
就不会作为即发即弃 Task
:
async void testBtn_Click(object sender, EventArgs e)
{
await Task.Run(() => notRunningOnUiThread(this));
}
感谢 Theodor Zoulias 的回答,我想我现在已经充分理解警告,知道我可以安全地抑制它。
这是因为委托 运行 的整个代码在 UI 消息循环的上下文中是 运行(因为 .BeginInvoke()
调用) 将报告任何异常。
这就是我的结局:
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Net5WinFormsApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = true; // For test purposes to prove that mustRunOnUiThreadAsync() runs on the UI thread.
}
void testBtn_Click(object sender, EventArgs e)
{
_ = Task.Run(() => notRunningOnUiThread(this)); // Not my real code, but this simulates it.
}
static void notRunningOnUiThread(Form1 ui) // Called from a non-UI thread and wants to call a method that updates the UI.
{
#pragma warning disable VSTHRD101 // Avoid unsupported async delegates
ui.BeginInvoke(new Action(async () => await ui.MustRunOnUiThreadAsync().ConfigureAwait(false)));
#pragma warning restore VSTHRD101 // Avoid unsupported async delegates
}
public async Task MustRunOnUiThreadAsync()
{
label1.Text = "Before update";
await Task.Delay(1000).ConfigureAwait(true);
label1.Text = "After update";
throw new InvalidOperationException("TEST"); // Check that this is reported.
}
}
}
或者,您可以将方法更改为 async void
:
static void notRunningOnUiThread(Form1 ui) // Called from a non-UI thread and wants to call a method that updates the UI.
{
ui.BeginInvoke(new Action(ui.MustRunOnUiThread));
}
#pragma warning disable VSTHRD100 // Avoid async void methods
public async void MustRunOnUiThread()
{
label1.Text = "Before update";
await Task.Delay(1000).ConfigureAwait(true);
label1.Text = "After update";
throw new InvalidOperationException("TEST"); // Check that this is reported.
}
#pragma warning restore VSTHRD100 // Avoid async void methods
这会将警告移至 MustRunOnUiThread()
,因此并没有太大帮助,尽管与 VSTHRD101
相比,该警告可能更广为人知。
请注意以下内容不起作用,因为它没有观察到任何异常(并且它也没有给出代码分析警告!):
static void notRunningOnUiThread(Form1 ui) // Called from a non-UI thread and wants to call a method that updates the UI.
{
ui.BeginInvoke(new Action(() => ui.MustRunOnUiThreadAsync().ConfigureAwait(false))); // BAD CODE! Misses exceptions.
}
public async Task MustRunOnUiThreadAsync()
{
label1.Text = "Before update";
await Task.Delay(1000).ConfigureAwait(true);
label1.Text = "After update";
throw new InvalidOperationException("TEST"); // Check that this is reported.
}
总结一下:
抑制针对特定情况的警告是安全的,因为可能抛出异常的代码在 UI 消息循环的上下文中是 运行,这将报告任何异常。
我正在重构一些旧的 WinForms 代码以使用 async/await,我遇到了代码分析警告,我不确定如何正确修复。
情况是我有一个从非UI线程调用的方法需要更新UI。旧代码使用历史悠久的技术调用 Control.BeginInvoke()
到 运行 UI 线程上的代码。
问题是我现在要调用的方法是 async
并且尝试使用 Control.BeginInvoke()
会产生警告 VSTHRD101
:“避免将异步 lambda 用于返回无效的委托类型,因为任何未被委托处理的异常都会导致进程崩溃。"
以下示例 WinForms 应用程序演示了该问题。这是一个简单的表单,仅包含一个名为“testBtn”的 Button
和一个名为“label1”的 Label
。
在 notRunningOnUiThread()
方法中调用 ControlBeginInvoke()
会导致警告,如评论所述:
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (disabled for brevity in this sample code)
namespace Net5WinFormsApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = true; // For test purposes to prove that mustRunOnUiThreadAsync() runs on the UI thread.
}
// This method is only to simulate what's happening in the real code.
// Assume that you cannot change this.
void testBtn_Click(object sender, EventArgs e)
{
_ = Task.Run(() => notRunningOnUiThread(this)); // Not my real code, but this simulates it.
}
// NOTE: In the real code this method is not a member of the Control,
// but it does have access to the Control to update.
void notRunningOnUiThread(Control ui) // Called from a non-UI thread and wants to call a method that updates the UI.
{
// The next line causes warning VSTHRD101:
// "Avoid using async lambda for a void returning delegate type,
// because any exceptions not handled by the delegate will crash the process."
ui.BeginInvoke(new Action(async () => await mustRunOnUiThreadAsync()));
}
async Task mustRunOnUiThreadAsync()
{
label1.Text = "Before update";
await Task.Delay(1000).ConfigureAwait(true);
label1.Text = "After update";
}
}
}
如果我抑制警告,即使 mustRunOnUiThreadAsync()
中有异常,一切似乎都 运行 正常(如果有异常,它会立即报告,而不是来自任务的终结器)。
我的问题是: 禁止显示此警告是否安全?如果不是,有什么好的修复方法?
我要解决的根本问题是如何从非[=64]线程安全地调用UI线程上的异步UI方法=] WinForms 应用程序中的线程,当只有 ui Form
可用且当前同步上下文不可用时。
(请注意,我不能在 notRunningOnUiThread()
中简单地 await mustRunOnUiThreadAsync()
。那会导致抛出“跨线程操作无效”InvalidOperationException
。)
我想一个可能的解决方案是使用 the advice from this answer 编写调用异步方法的非异步方法。但这是针对这种特殊情况的最佳方法吗?
[编辑]
我应该提到:在我的实际代码中,notRunningOnUiThread()
不是需要更新 UI 的 Control
的成员。因为 notRunningOnUiThread()
不是 运行 在 UI 线程上,所以我不能使用 System.Threading.SynchronizationContext
,因为它是错误的上下文。但是,我确实有 Control
可用于调用 Control.BeginInvoke()
。
实际情况是我正在为一个 RPC 请求提供服务,该请求必须使用恰好是异步的方法更新一些 UI。
一个想法可能是将 mustRunOnUiThreadAsync
从 async Task
转换为 async void
。从概念上讲,您可以说此方法是源自后台线程的“事件”的“事件处理程序”,因此您基本上没有违反 Microsoft's guidelines 关于 async void
的用法。示例:
async void mustRunOnUiThread()
{
label1.Text = "Before update";
await Task.Delay(1000).ConfigureAwait(true);
label1.Text = "After update";
}
...并且在 notRunningOnUiThread
方法中:
ui.BeginInvoke(new Action(() => mustRunOnUiThread()));
此修改不会以任何方式改变您当前代码的行为,除了使 VSTHRD101 警告消失。
作为旁注,您还可以考虑将 testBtn_Click
处理程序转换为 async void
,这样 notRunningOnUiThread
就不会作为即发即弃 Task
:
async void testBtn_Click(object sender, EventArgs e)
{
await Task.Run(() => notRunningOnUiThread(this));
}
感谢 Theodor Zoulias 的回答,我想我现在已经充分理解警告,知道我可以安全地抑制它。
这是因为委托 运行 的整个代码在 UI 消息循环的上下文中是 运行(因为 .BeginInvoke()
调用) 将报告任何异常。
这就是我的结局:
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Net5WinFormsApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = true; // For test purposes to prove that mustRunOnUiThreadAsync() runs on the UI thread.
}
void testBtn_Click(object sender, EventArgs e)
{
_ = Task.Run(() => notRunningOnUiThread(this)); // Not my real code, but this simulates it.
}
static void notRunningOnUiThread(Form1 ui) // Called from a non-UI thread and wants to call a method that updates the UI.
{
#pragma warning disable VSTHRD101 // Avoid unsupported async delegates
ui.BeginInvoke(new Action(async () => await ui.MustRunOnUiThreadAsync().ConfigureAwait(false)));
#pragma warning restore VSTHRD101 // Avoid unsupported async delegates
}
public async Task MustRunOnUiThreadAsync()
{
label1.Text = "Before update";
await Task.Delay(1000).ConfigureAwait(true);
label1.Text = "After update";
throw new InvalidOperationException("TEST"); // Check that this is reported.
}
}
}
或者,您可以将方法更改为 async void
:
static void notRunningOnUiThread(Form1 ui) // Called from a non-UI thread and wants to call a method that updates the UI.
{
ui.BeginInvoke(new Action(ui.MustRunOnUiThread));
}
#pragma warning disable VSTHRD100 // Avoid async void methods
public async void MustRunOnUiThread()
{
label1.Text = "Before update";
await Task.Delay(1000).ConfigureAwait(true);
label1.Text = "After update";
throw new InvalidOperationException("TEST"); // Check that this is reported.
}
#pragma warning restore VSTHRD100 // Avoid async void methods
这会将警告移至 MustRunOnUiThread()
,因此并没有太大帮助,尽管与 VSTHRD101
相比,该警告可能更广为人知。
请注意以下内容不起作用,因为它没有观察到任何异常(并且它也没有给出代码分析警告!):
static void notRunningOnUiThread(Form1 ui) // Called from a non-UI thread and wants to call a method that updates the UI.
{
ui.BeginInvoke(new Action(() => ui.MustRunOnUiThreadAsync().ConfigureAwait(false))); // BAD CODE! Misses exceptions.
}
public async Task MustRunOnUiThreadAsync()
{
label1.Text = "Before update";
await Task.Delay(1000).ConfigureAwait(true);
label1.Text = "After update";
throw new InvalidOperationException("TEST"); // Check that this is reported.
}
总结一下:
抑制针对特定情况的警告是安全的,因为可能抛出异常的代码在 UI 消息循环的上下文中是 运行,这将报告任何异常。