During creating simple sample using async/await
, I discovered, that some examples just illustrate the pattern on Button1_Click
like methods and freely update GUI controls directly from async
methods. So one could consider this as the safe mechanism. But my test code was constantly crashing on TargetInvocationException
exceptions in mscorlib.dll
with inner exceptions like: NullReference
, ArgumentOutOfRange
etc. Regarding the stack trace, everything seemed to point to the WinForms.StatusStrip
labels displaying results (and being driven directly from the async
methods bound to the button event handlers). The crashing seems to be fixed when using the old school Control.Invoke
when accessing the GUI controls.
The questions are: Have I missed something important? Are the async methods usafe the same way as the threads/background workers formerly used for long term operations and thus the Invoke
is the recommended solution? Are the code snippets driving GUI directly from async
methods wrong?
EDIT: For the missing-source downvoters: Create a Simple Form containing three buttons and one StatusStrip containing two labels...
//#define OLDSCHOOL_INVOKE
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AsyncTests
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void LongTermOp()
{
int delay;
int thisId;
lock (mtx1)
{
delay = rnd.Next(2000, 10000);
thisId = firstCount++;
#if OLDSCHOOL_INVOKE
Invoke(new Action(() =>
#endif
label1Gen.Text = $"Generating first run delay #{thisId} of {delay} ms"
#if OLDSCHOOL_INVOKE
))
#endif
;
++firstPending;
}
await Task.Delay(delay);
lock (mtx1)
{
--firstPending;
#if OLDSCHOOL_INVOKE
Invoke(new Action(() =>
#endif
label1Gen.Text = $"First run #{thisId} completed, {firstPending} pending..."
#if OLDSCHOOL_INVOKE
))
#endif
;
}
}
private async Task LongTermOpAsync()
{
await Task.Run((Action)LongTermOp);
}
private readonly Random rnd = new Random();
private readonly object mtx1 = new object();
private readonly object mtx2 = new object();
private int firstCount;
private int firstPending;
private int secondCount;
private int secondPending;
private async void buttonRound1_Click(object sender, EventArgs e)
{
await LongTermOpAsync();
}
private async void buttonRound2_Click(object sender, EventArgs e)
{
await Task.Run(async () =>
{
int delay;
int thisId;
lock (mtx2)
{
delay = rnd.Next(2000, 10000);
thisId = secondCount++;
#if OLDSCHOOL_INVOKE
Invoke(new Action(() =>
#endif
label2Gen.Text = $"Generating second run delay #{thisId} of {delay} ms"
#if OLDSCHOOL_INVOKE
))
#endif
;
++secondPending;
}
await Task.Delay(delay);
lock (mtx2)
{
--secondPending;
#if OLDSCHOOL_INVOKE
Invoke(new Action(() =>
#endif
label2Gen.Text = $"Second run #{thisId} completed, {secondPending} pending..."
#if OLDSCHOOL_INVOKE
))
#endif
;
}
});
}
private void buttonRound12_Click(object sender, EventArgs e)
{
buttonRound1_Click(sender, e);
buttonRound2_Click(sender, e);
}
private bool isRunning = false;
private async void buttonCycle_Click(object sender, EventArgs e)
{
isRunning = !isRunning;
await Task.Run(() =>
{
while (isRunning)
{
buttonRound12_Click(sender, e);
Application.DoEvents();
}
});
}
}
}