I'm refactoring some old WinForms code to use async/await, and I've hit a code analysis warning that I'm not sure how to fix properly.
The situation is that I have a method that is called from a non-UI thread which needs to update the UI. The old code uses the time-honoured technique of calling Control.BeginInvoke()
to run the code on the UI thread.
The problem is that the method I want to invoke is now async
and attempting to use Control.BeginInvoke()
yields the warning VSTHRD101
: "Avoid using async lambda for a void returning delegate type, because any exceptions not handled by the delegate will crash the process."
The following sample WinForms application demonstrates the problem. It's a simple form containing only a Button
called "testBtn" and a Label
called "label1".
The call to ControlBeginInvoke()
inside the notRunningOnUiThread()
method causes the warning as commented:
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";
}
}
}
If I suppress the warning, everything seems to run OK even if there are exceptions in mustRunOnUiThreadAsync()
(if there's an exception, it's reported immediately and not from the finalizer for a Task).
My question is: Is it safe to suppress this warning? And if not, what's a good way to fix it?
The fundamental thing I want to solve is how to safely call an async UI method on the UI thread from a non-UI thread in a WinForms application, when only the ui Form
is available and the current synchronisation context is not available.
(Note that I can't simply await mustRunOnUiThreadAsync()
in notRunningOnUiThread()
. That would cause a "Cross-thread operation not valid" InvalidOperationException
to be thrown.)
I suppose one possible solution is to write a non-async method that calls the async method, using the advice from this answer. But is that the best approach for this particular case?
[EDIT]
I should have mentioned: In my actual code the notRunningOnUiThread()
is not a member of the Control
that needs to update the UI. Because notRunningOnUiThread()
isn't running on a UI thread, I can't use System.Threading.SynchronizationContext
since it is the wrong context. I do have the Control
available for calling Control.BeginInvoke()
however.
The actual situation is that I am servicing an RPC request which must update some UI using a method that happens to be async.