I am trying to iterate through a for loop by pressing start button and stop it by pressing stop button. I am using await Task.Run(() =>
it works in the expected manner. But when I press start button again, I get TargetInvokationException in Application.Run(new Form1());
.
My code below
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CancellationTest
{
public partial class Form1 : Form
{
private readonly SynchronizationContext synchronizationContext;
private DateTime previousTime = DateTime.Now;
CancellationTokenSource cts = new CancellationTokenSource();
public Form1()
{
InitializeComponent();
synchronizationContext = SynchronizationContext.Current;
}
private async void ButtonClickHandlerAsync(object sender, EventArgs e)
{
button1.Enabled = false;
var count = 0;
CancellationToken token = cts.Token;
await Task.Run(() =>
{
try
{
for (var i = 0; i <= 5000000; i++)
{
token.ThrowIfCancellationRequested();
UpdateUI(i);
count = i;
}
}
catch (System.OperationCanceledException)
{
MessageBox.Show("Canceled");
}
}, token);
label1.Text = @"Counter " + count;
button1.Enabled = true;
}
public void UpdateUI(int value)
{
var timeNow = DateTime.Now;
if ((DateTime.Now - previousTime).Milliseconds <= 50) return;
synchronizationContext.Post(new SendOrPostCallback(o =>
{
label1.Text = @"Counter " + (int)o;
}), value);
previousTime = timeNow;
}
private void button2_Click(object sender, EventArgs e)
{
cts.Cancel();
}
}
}
Can anyone explain why this happens and how to resolve this.
Can anyone explain why this happens
TargetInvokationException
is a wrapper type exception and the main information is contained in InnerException
property which you didn't show. Looking at the code, most likely it is OperationCanceledException
and is caused by passing an already canceled token to Task.Run
.
In general you should not reuse the CancellationTokenSource
instance - see The general pattern for implementing the cooperative cancellation model in the Remarks section of the documentation.
Also you should protect with try/catch
the whole block, not only the Task.Run
body. And initialize all the involved state members at the beginning, and do the necessary cleanup at the end.
So the correct code could be like this:
private DateTime previousTime;
private CancellationTokenSource cts;
private async void ButtonClickHandlerAsync(object sender, EventArgs e)
{
button1.Enabled = false;
var count = 0;
previousTime = DateTime.Now;
cts = new CancellationTokenSource();
try
{
CancellationToken token = cts.Token;
await Task.Run(() =>
{
for (var i = 0; i <= 5000000; i++)
{
token.ThrowIfCancellationRequested();
UpdateUI(i);
count = i;
}
}, token);
}
catch (System.OperationCanceledException)
{
MessageBox.Show("Canceled");
}
finally
{
cts.Dispose();
cts = null;
}
label1.Text = @"Counter " + count;
button1.Enabled = true;
}
and make sure Cancel
button is enabled only when cts != null
or check that condition inside the click handler in order to avoid NRE.