I'm toying around with C# and trying to practice some WinForms with async code. I have the following code, but when I click the button and initiate doTheThingAsync
and I close the window while the task is still processing then Visual Studio catches a NullReferenceException
from the point where I access toolStripProgressBar1.Value
.
private async Task doTheThingAsync(IProgress<int> progress)
{
await Task.Run(() =>
{
for (int i = 1; i <= 1000; ++i)
{
Thread.Sleep(10);
progress.Report(i);
}
Thread.Sleep(2000);
});
}
private async void button1_Click(object sender, EventArgs e)
{
var progress = new Progress<int>(i => toolStripProgressBar1.Value = i);
await doTheThingAsync(progress);
toolStripProgressBar1.Value = 0;
}
Not sure why this is happening, but I figure if I cancel the task before the Form closes then all will be well, so I modify it.
private async Task doTheThingAsync(IProgress<int> progress, CancellationToken ct)
{
await Task.Run(() =>
{
for (int i = 1; i <= 1000; ++i)
{
if (ct.IsCancellationRequested) return;
Thread.Sleep(10);
progress.Report(i);
}
Thread.Sleep(2000);
});
}
private async void button1_Click(object sender, EventArgs e)
{
var progress = new Progress<int>(i => toolStripProgressBar1.Value = i);
var cts = new CancellationTokenSource();
FormClosed += (s, ev) => cts.Cancel();
await doTheThingAsync(progress, cts.Token);
toolStripProgressBar1.Value = 0;
}
No dice. I still get the exception. So out of desperation I try wrapping it in a try/catch.
var progress = new Progress<int>(i => {
try {
toolStripProgressBar1.Value = i;
} catch {}
});
But even then the uncaught NullReferenceException
persists, and I am out of ideas.
System.NullReferenceException
HResult=0x80004003
Message=Object reference not set to an instance of an object.
Source=System.Windows.Forms
StackTrace:
at System.Windows.Forms.ToolStripProgressBar.set_Value(Int32 value)
at Test.Form1.<>c__DisplayClass2_0.<button1_Click>b__0(Int32 i) in C:\Users\Chris\source\repos\Test\Test\Form1.cs:line 37
Edit: The NullReferenceException
seems to come from the fact that ToolStripProgressBar.Value
has a { get, set } that accesses a reference that is nulled due to the fact that the control has already been disposed at the time of access.
Fildor's request:
private Task doTheThingAsync(IProgress<int> progress, CancellationToken ct)
{
return Task.Run(() =>
{
for (int i = 1; i <= 1000; ++i)
{
ct.ThrowIfCancellationRequested(); //VS breaks here with unhandled exception
Thread.Sleep(10);
progress.Report(i);
}
Thread.Sleep(2000);
});
}
private async void button1_Click(object sender, EventArgs e)
{
var progress = new Progress<int>(i => {
toolStripProgressBar1.Value = i;
});
var cts = new CancellationTokenSource();
FormClosed += (s, ev) => cts.Cancel();
try
{
await doTheThingAsync(progress, cts.Token);
toolStripProgressBar1.Value = 0;
}
catch (OperationCanceledException) { }
}
Edit: The issues with the above code is that is should have been TaskCanceledException
not OperationCanceledException
.
Update: This is the only way I have been able to get this to work such that I can close the form without getting an unhandled exception. Note that I am having to always check IsDisposed
before accesing Value
on the ToolStripProgressBar
from within the IProgress action. I can't say for sure why but this seems absurd to me.
private Task doTheThingAsync(IProgress<int> progress, CancellationToken ct)
{
return Task.Run(async () =>
{
for (int i = 1; i <= 1000; ++i)
{
ct.ThrowIfCancellationRequested();
await Task.Delay(10, ct);
progress.Report(i);
}
await Task.Delay(2000, ct);
});
}
private async void button1_Click(object sender, EventArgs e)
{
var progress = new Progress<int>(i => {
if (!toolStripProgressBar1.IsDisposed) toolStripProgressBar1.Value = i;
});
var cts = new CancellationTokenSource();
//FormClosed += (s, ev) => cts.Cancel(); //no longer actually necessary...
try
{
await doTheThingAsync(progress, cts.Token);
toolStripProgressBar1.Value = 0;
}
catch {}
}
Edit Changed from label
to report result to a ToolStripProgressBar
which as stated from others needs to check _cts.IsCancellationRequested
Here is a code sample to try out which will not throw an exception on closing the form/app while a do nothing async method is running.
Starts off in a button named StartButton, first checks if the CancellationTokenSource needs to be recreated if used already, performs a do nothing method using await Task.Delay(500, ct)
rather than Thread.Sleep
which has already been pointed out to avoid.
In the do nothing method AsyncMethod
we test if there is a cancel request which goes back to the start button code which handles the request in a try/catch.
Cancellation is done in a button named CancelButton.
Progress is done via setting value property of a ToolStripProgressBar
.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AsyncSimple
{
public partial class Form1 : Form
{
private CancellationTokenSource _cts = new ();
public Form1()
{
InitializeComponent();
}
private async void StartButton_Click(object sender, EventArgs e)
{
if (_cts.IsCancellationRequested)
{
_cts.Dispose();
_cts = new CancellationTokenSource();
}
var progressIndicator = new Progress<int>(ReportProgress);
try
{
await AsyncMethod(progressIndicator, _cts.Token);
}
catch (OperationCanceledException)
{
// do nothing
}
}
private void CancelButton_Click(object sender, EventArgs egEventArgs)
{
_cts.Cancel();
}
private static async Task AsyncMethod(IProgress<int> progress, CancellationToken ct)
{
for (int index = 1; index <= 100; index++)
{
//Simulate an async call that takes some time to complete
await Task.Delay(500, ct);
progress?.Report(index);
}
}
private void ReportProgress(int value)
{
if (_cts.IsCancellationRequested)
{
return;
}
else
{
toolStripProgressBar1.Value = value;
}
}
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.