I am new to Dataflow, and I follow this walkthrough How to: Cancel a Dataflow Block.
I click add button first, and then click cancel, but I got exception about "A task was canceled Exception" after clicking cancel button. I fail to find any way to resolve this error.
Any help would be appreciated.
Update:
Code for demo:
public partial class Form1 : Form
{
CancellationTokenSource cancellationTokenSource;
TransformBlock<WorkItem, WorkItem> startWork;
ActionBlock<WorkItem> completeWork;
ActionBlock<ToolStripProgressBar> incProgress;
ActionBlock<ToolStripProgressBar> decProgress;
TaskScheduler uiTaskScheduler;
public Form1()
{
InitializeComponent();
uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Cancel.Enabled = false;
}
private void Add_Click(object sender, EventArgs e)
{
if (!Cancel.Enabled)
{
CreatePipeline();
Cancel.Enabled = true;
}
for (int i = 0; i < 20; i++)
{
toolStripProgressBar1.Value++;
startWork.Post(new WorkItem());
}
}
private async void Cancel_Click(object sender, EventArgs e)
{
Add.Enabled = false;
Cancel.Enabled = false;
cancellationTokenSource.Cancel();
try
{
await Task.WhenAll(
completeWork.Completion,
incProgress.Completion,
decProgress.Completion);
}
catch (OperationCanceledException)
{
throw;
}
toolStripProgressBar4.Value += toolStripProgressBar1.Value;
toolStripProgressBar4.Value += toolStripProgressBar2.Value;
// Reset the progress bars that track the number of active work items.
toolStripProgressBar1.Value = 0;
toolStripProgressBar2.Value = 0;
// Enable the Add Work Items button.
Add.Enabled = true;
}
private void CreatePipeline()
{
cancellationTokenSource = new CancellationTokenSource();
startWork = new TransformBlock<WorkItem, WorkItem>(workItem =>
{
workItem.DoWork(250, cancellationTokenSource.Token);
decProgress.Post(toolStripProgressBar1);
incProgress.Post(toolStripProgressBar2);
return workItem;
},
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationTokenSource.Token
});
completeWork = new ActionBlock<WorkItem>(workItem =>
{
workItem.DoWork(1000, cancellationTokenSource.Token);
decProgress.Post(toolStripProgressBar2);
incProgress.Post(toolStripProgressBar3);
},
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationTokenSource.Token,
MaxDegreeOfParallelism = 2
});
startWork.LinkTo(completeWork);
startWork.Completion.ContinueWith(delegate { completeWork.Complete(); },cancellationTokenSource.Token);
incProgress = new ActionBlock<ToolStripProgressBar>(progress =>
{
progress.Value++;
},
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationTokenSource.Token,
TaskScheduler = uiTaskScheduler
});
decProgress = new ActionBlock<ToolStripProgressBar>(progress => progress.Value--,
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationTokenSource.Token,
TaskScheduler = uiTaskScheduler
});
}
class WorkItem
{
public void DoWork(int milliseconds, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested == false)
{
Thread.Sleep(milliseconds);
}
}
}
}
As #SirRufo pointed out, the solution to your question is simply don't re-throw the exception after you've caught it. But to highlight some of the other techniques you can use with dataflow as discussed in the comments I put together a small sample. I've tried to keep the spirit and intent of your original code intact. To that end; the original code didn't show how the flow would complete normally, as opposed to cancelled, so I left it out here as well.
using System;
using System.Data;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
namespace WindowsFormsApp1 {
public partial class Form1 : Form {
private CancellationTokenSource cancellationTokenSource;
private TransformBlock<WorkItem, WorkItem> startWork;
private ActionBlock<WorkItem> completeWork;
private IProgress<int> progressBar1Value;
private IProgress<int> progressBar2Value;
public Form1() {
InitializeComponent();
btnCancel.Enabled = false;
}
private async void btnAdd_Click(object sender, EventArgs e) {
if(!btnCancel.Enabled) {
CreatePipeline();
btnCancel.Enabled = true;
}
var data = Enumerable.Range(0, 20).Select(_ => new WorkItem());
foreach(var workItem in data) {
await startWork.SendAsync(workItem);
progressBar1.Value++;
}
}
private async void btnCancel_Click(object sender, EventArgs e) {
btnAdd.Enabled = false;
btnCancel.Enabled = false;
cancellationTokenSource.Cancel();
await completeWork.Completion.ContinueWith(tsk => this.Invoke(new Action(() => this.Text = "Flow Cancelled")),
TaskContinuationOptions.OnlyOnCanceled);
progressBar4.Value += progressBar1.Value;
progressBar4.Value += progressBar2.Value;
// Reset the progress bars that track the number of active work items.
progressBar1.Value = 0;
progressBar2.Value = 0;
// Enable the Add Work Items button.
btnAdd.Enabled = true;
}
private void CreatePipeline() {
cancellationTokenSource = new CancellationTokenSource();
progressBar1Value = new Progress<int>(_ => progressBar1.Value++);
progressBar2Value = new Progress<int>(_ => progressBar2.Value++);
startWork = new TransformBlock<WorkItem, WorkItem>(async workItem => {
await workItem.DoWork(250, cancellationTokenSource.Token);
progressBar1Value.Report(0); //Value is ignored since the progressbar value is simply incremented
progressBar2Value.Report(0); //Value is ignored since the progressbar value is simply incremented
return workItem;
},
new ExecutionDataflowBlockOptions {
CancellationToken = cancellationTokenSource.Token
});
completeWork = new ActionBlock<WorkItem>(async workItem => {
await workItem.DoWork(1000, cancellationTokenSource.Token);
progressBar1Value.Report(0); //Value is ignored since the progressbar value is simply incremented
progressBar2Value.Report(0); //Value is ignored since the progressbar value is simply incremented
},
new ExecutionDataflowBlockOptions {
CancellationToken = cancellationTokenSource.Token,
MaxDegreeOfParallelism = 2
});
startWork.LinkTo(completeWork, new DataflowLinkOptions() { PropagateCompletion = true });
}
}
public class WorkItem {
public async Task DoWork(int milliseconds, CancellationToken cancellationToken) {
if(cancellationToken.IsCancellationRequested == false) {
await Task.Delay(milliseconds);
}
}
}
}
After checking the code, I released that the tasks will be cancelled if I click Cancel.
await Task.WhenAll(
completeWork.Completion,
incProgress.Completion,
decProgress.Completion);
But, above code Task.WhenAll need all of the tasks return complete status, then the "A task was canceled Exception" throw as expected if it returned cancelled instead of completed.
For a possible way to resolve this issue, we should return Task completed if we cancelled the task, and the code below works for me.
await Task.WhenAll(
completeWork.Completion.ContinueWith(task => cancelWork(task, "completeWork"), TaskContinuationOptions.OnlyOnCanceled),
incProgress.Completion.ContinueWith(task => cancelWork(task, "incProgress"), TaskContinuationOptions.OnlyOnCanceled),
decProgress.Completion.ContinueWith(task => cancelWork(task, "decProgress"), TaskContinuationOptions.OnlyOnCanceled));
Is it reasonable?
Related
I am a newbie with C#. I am trying to get a task to run in parallel to a stored procedure execution. So for example - I have a stored procedure which in this instance will just be running WAITFOR DELAY '00:00:10'. During that ten seconds a label will be populated with text which will have full stops added and then remove as you would see in a loading screen.
Disabling Replication.
Disabling Replication..
Disabling Replication...
Once the proc is completed it will break out of the while loop. This is being handled by a boolean value. So _IsRunning = false, run stored procedure then set IsRunning = true. The code works exactly as I would want it to if I just use Thread.Sleep(1000), which I used for test purposes, instead of a stored procedure.
Can someone tell me why this will not work when using the stored procedure? It just gets stuck on the while loop and constantly says Disabling Replication with the loading full stops.
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AsynchronousCoding
{
public partial class Form1 : Form
{
private DataAccess _access = new DataAccess();
public Form1()
{
InitializeComponent();
}
private async Task LoadRapport()
{
await DisableReplication();
}
private async Task DisableReplication()
{
Task.Factory.StartNew(TestMethod);
await Task.Factory.StartNew(() => ShowProgressText("Disabling Replication"));
}
private bool _IsRunning;
private void TestMethod()
{
_isRunning= false;
//Thread.Sleep(10000);
_access.Rapport_ReplicationSetting();
_isRunning= true;
}
private void ShowProgressText(string txt)
{
var count = 0;
var logText = new StringBuilder();
logText.Append(txt);
var baseLen = logText.Length;
while (!_isRunning)
{
Thread.Sleep(250);
if (count >= 3)
{
logText.Remove(baseLen, count);
count = 0;
}
logText.Append(".");
count++;
BeginInvoke(new Action(() => { UpdateProgressText(logText.ToString()); }));
}
BeginInvoke(new Action(() => { UpdateProgressText(txt + " - Complete"); }));
Thread.Sleep(2000);
}
private void UpdateProgressText(string txt)
{
lblProgress.Text = txt;
}
private void button1_Click(object sender, EventArgs e)
{
LoadRapport();
}
}
}
Consider this simple example with CancellationToken usage. I don't suggest bool, it's not Thread-safe.
private async void button1_Click(object sender, EventArgs e)
{
IProgress<string> progress = new Progress<string>(s => lblProgress.Text = s);
using (CancellationTokenSource cts = new CancellationTokenSource());
{
Task animationTask = ProgressAnimationAsync(progress, cts.Token);
await DoSomeJobAsync();
// await Task.Run(() => DoSomeHeavyJob()); // uncomment for test
cts.Cancel();
await animationTask;
}
}
private async Task ProgressAnimationAsync(IProgress progress, CancellationToken token)
{
int i = 1;
while (!token.IsCancellationRequested)
{
progress.Report("Loading" + new string("." , i));
i = i == 3 ? 1 : i + 1;
await Task.Delay(250);
}
}
// as I/O-bound operation
private async Task DoSomeJobAsync()
{
await Task.Delay(10000);
}
// as CPU-bound operation
private void DoSomeHeavyJob()
{
Thread.Sleep(10000);
}
There's two different methods, you can test both.
Note: it's safe to use lblProgress.Text directly here, without IProgress synchronized callback. It's given here just for example. Create new Progress in UI Thread and you may call .Report() from any other Thread safely without Invoke or BeginInvoke.
You can learn more about X-bound operations here.
Anyone know what I'm doing wrong?
Windows 10 - VS2013
C# .NET 4.5.1
I have a button called testButton and a testButton_Click method that I'm trying to use to do work, but also be able to abort that work. Doing the work in a task is working, and the logic for aborting is being executed (the button turns back to a grey "Run Test"), but the cts.Cancel method doesn't seem to affect the token's IsCancellationRequested flag, or the task has the wrong token, or something...?
public System.Threading.Tasks.Task DoWorkAsync(CancellationToken ct = default(CancellationToken))
{
System.Threading.Tasks.Task T1 = new System.Threading.Tasks.Task(() =>
{
try
{
var capturedToken = ct;
// do work here
for (int i = 0; i < work.Length; i++)
{
ct.ThrowIfCancellationRequested();
capturedToken.ThrowIfCancellationRequested();
if (ct.IsCancellationRequested) throw new OperationCanceledException(ct);
// sleep 1 second
System.Threading.Thread.Sleep(1000);
}
}
catch
{
MessageBox.Show("Caught :) I can do stuff", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
taskRunning = false;
}
}, ct);
return T1;
}
private async void testButton_Click(object sender, EventArgs e)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
var task = DoWorkAsync(ct);
if (!taskRunning)
{
Cursor.Current = Cursors.WaitCursor;
testButton.BackColor = Color.Red;
testButton.Text = "ABORT";
taskRunning = true;
task.Start();
await task;
// Release cursor when setup is complete
Cursor.Current = Cursors.Default;
}
else
{
cts.Cancel();
testButton.Text = "Run Test";
testButton.BackColor = Color.Gray;
}
}
I have the following threaded code ( i think ) form with a start and cancel button and a multilined text box, the commented sections (//) are from the working single threaded version and below i have tried to retrofit the multithreaded task.factory parts, however it seems to launch the powershell commands fine ( show in task manager ) but the program completes without waiting for the results from each "heavyOperation".
The idea is to start all the four HeavyOperation tasks at the same time (ish) and wait for each to return the results and append the results to the text box
public partial class Form1 : Form
{
Progress<string> progressReporter = new Progress<string>();
CancellationTokenSource cancelSource;
public Form1()
{
InitializeComponent();
progressReporter.ProgressChanged += progressManager_ProgressChanged;
}
async private void btnStart_Click(object sender, EventArgs e)
{
btnStart.Enabled = false;
btnCancel.Enabled = true;
cancelSource = new CancellationTokenSource();
//textBox1.Text = await Task.Run(() => PerfromTaskAction(cancelSource.Token));
await Task.Run(() => PerfromTaskAction(cancelSource.Token));
lblStatus.Text = "Completed."; btnStart.Enabled = true;
btnCancel.Enabled = false;
}
//private string PerfromTaskAction(CancellationToken ct)
static void PerfromTaskAction(CancellationToken ct)
{
//StringBuilder sb = new StringBuilder();
object[] arrObjects = new object[] { "SERVER1", "SERVER2", "SERVER3", "SERVER4" };
foreach(object i in arrObjects)
{
//if (ct.IsCancellationRequested) break;
//sb.Append(string.Format("{0}: {1}\r\n", HeavyOperation(i.ToString()),i));
//((IProgress<string>)progressReporter).Report(string.Format("Now Checking: {0}...", i));
Task.Factory.StartNew(() => HeavyOperation(i.ToString()));
}
//return sb.ToString();
}
void progressManager_ProgressChanged(object sender, string e)
{
lblStatus.Invoke((Action)(() => lblStatus.Text = e));
}
//private string HeavyOperation(string i)
public static void HeavyOperation(string i)
{
PowerShell ps = PowerShell.Create();
ps.AddCommand("invoke-command");
ps.AddParameter("computername", i);
ps.AddParameter("scriptblock", ScriptBlock.Create("get-vmreplication | select State"));
Collection<PSObject> result = ps.Invoke();
//return result[0].Properties["State"].Value.ToString();
Console.Write(result[0].Properties["State"].Value.ToString());
}
private void btnCancel_Click(object sender, EventArgs e)
{
cancelSource.Cancel();
}
}
Thanks for looking
You also need to await for the HeavyOperations to complete.
You can use Task.WhenAll for this purpose. Here is an async version of PerformTaskAction, using Task.WhenAll
I've taken into account Scott Chamberlain's suggestions:
Changed the unsafe (in async-await context) Task.Factory.StartNew() to Task.Run()
Removed the unnecessary await in the invocation of PerformTaskAction()
Passed the missing CancellationToken in the outer Task.Run() call
static async Task PerfromTaskAction(CancellationToken ct) {
//StringBuilder sb = new StringBuilder();
object[] arrObjects = new object[] { "SERVER1", "SERVER2", "SERVER3", "SERVER4" };
IList<Task> tasks = new List<Task>(); // collect all tasks in single collection
foreach( object i in arrObjects ) {
//if (ct.IsCancellationRequested) break;
//sb.Append(string.Format("{0}: {1}\r\n", HeavyOperation(i.ToString()),i));
//((IProgress<string>)progressReporter).Report(string.Format("Now Checking: {0}...", i));
tasks.Add(Task.Run(() => HeavyOperation(i.ToString())));
}
await Task.WhenAll(tasks).ConfigureAwait(false); // wait asynchronously for all tasks to complete
}
Now that PerformTaskAction is async, you also need to await on it.
Finally you invoke PerformTaskAction, making sure you also pass the CancellationToken.
await Task.Run( ()=> PerformTaskAction(cancelSource.Token), cancelSource.Token);
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.
I am trying to update a progressbar in a multithreaded environment. I know that a lot of questions already treat that question but none of the proposed solution have worked for me.
Here is the backbone of my code :
public static void DO_Computation(//parameters) {
//Intialisation of parameters
Parallel.For(struct initialisation with local data) {
//business logic
//Call to update_progressbar (located in an another class, as the DO_Computation function is in Computation.cs class (not deriving from Form).
WinForm.Invoke((Action)delegate {Update_Progress_Bar(i);}); //WinForm is a class that exposes the progressbar.
}
}
This is not working (the progressbar is freezing when arriving at 100%, which is normal (we can refer to the microsoft article in this matter (indeed, this is not a thread-safe operating method)).
The Microsoft site stiplates to wrap the Parallel.For loop into a Task routine as follows:
public static void DO_Computation(//parameters) {
//Intialisation of parameters
Task.Factory.StartNew(() =>
{
Parallel.For(struct initialosation with local data) {
//business logic
//Call to update_progressbar (ocated in an another class, as the DO_Computation function is in Computation.cs class (not deriving from Form).
WinForm.Invoke((Action)delegate {Update_Progress_Bar(i);}); //WinForm is a class that exposes the progressbar.
..
}
});
});
However this is not working as well, when debugging the thread is getting out of the Task scope directly.
EDIT 2:
Basically, my problem is divided in 3 parts: Computation.cs (where DO_Computation is exposed), WinForm which is the form containing the progress bar, and MainWindow which is the form that contains the button which when clicked opens the form with the progress bar.
I do not clearly understand what is the use of "Task" in this case.
Because it is going out of the Task scope without performing any Parallel.For work
Any ideas?
Many Thanks,
EDIT 3:
I upgraded my code with the help of Noseratio (thans a lot to him). However I have the same problem which is the code inside task is never executed. My code now looks like :
DoComputation method
//Some Initilasations here
Action enableUI = () =>
{
frmWinProg.SetProgressText("Grading Transaction...");
frmWinProg.ChangeVisibleIteration(true);
};
Action<Exception> handleError = (ex) =>
{
// error reporting
MessageBox.Show(ex.Message);
};
var cts = new CancellationTokenSource();
var token = cts.Token;
Action cancel_work = () =>
{
frmWinProg.CancelTransaction();
cts.Cancel();
};
var syncConext = SynchronizationContext.Current;
Action<int> progressReport = (i) =>
syncConext.Post(_ => frmWinProg.SetIteration(i,GrpModel2F.NumOfSim, true), null);
var task = Task.Factory.StartNew(() =>
{
ParallelLoopResult res = Parallel.For<LocalDataStruct>(1,NbSim, options,
() => new DataStruct(//Hold LocalData for each thread),
(iSim, loopState, DataStruct) =>
//Business Logic
if (token.IsCancellationRequested)
{
loopState.Stop();
}
progressReport(iSim);
//Business Logic
return DataStruct;
},
(DataStruct) =>
//Assiginig Results;
});//Parallel.For end
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
task.ContinueWith(_ =>
{
try
{
task.Wait();
}
catch (Exception ex)
{
while (ex is AggregateException && ex.InnerException != null)
ex = ex.InnerException;
handleError(ex);
}
enableUI();
}, TaskScheduler.FromCurrentSynchronizationContext
());
Note that the Do_Computation function is itself called from a Form that runs a BackGroundWorker on it.
Use async/await, Progress<T> and observe cancellation with CancellationTokenSource.
A good read, related: "Async in 4.5: Enabling Progress and Cancellation in Async APIs".
If you need to target .NET 4.0 but develop with VS2012+ , you still can use async/await, Microsoft provides the Microsoft.Bcl.Async library for that.
I've put together a WinForms example illustrating all of the above. It also shows how to observe cancellation for Parallel.For loop, using ParallelLoopState.Stop():
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication_22487698
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
IEnumerable<int> _data = Enumerable.Range(1, 100);
Action _cancelWork;
private void DoWorkItem(
int[] data,
int item,
CancellationToken token,
IProgress<int> progressReport,
ParallelLoopState loopState)
{
// observe cancellation
if (token.IsCancellationRequested)
{
loopState.Stop();
return;
}
// simulate a work item
Thread.Sleep(500);
// update progress
progressReport.Report(item);
}
private async void startButton_Click(object sender, EventArgs e)
{
// update the UI
this.startButton.Enabled = false;
this.stopButton.Enabled = true;
try
{
// prepare to handle cancellation
var cts = new CancellationTokenSource();
var token = cts.Token;
this._cancelWork = () =>
{
this.stopButton.Enabled = false;
cts.Cancel();
};
var data = _data.ToArray();
var total = data.Length;
// prepare the progress updates
this.progressBar.Value = 0;
this.progressBar.Minimum = 0;
this.progressBar.Maximum = total;
var progressReport = new Progress<int>((i) =>
{
this.progressBar.Increment(1);
});
// offload Parallel.For from the UI thread
// as a long-running operation
await Task.Factory.StartNew(() =>
{
Parallel.For(0, total, (item, loopState) =>
DoWorkItem(data, item, token, progressReport, loopState));
// observe cancellation
token.ThrowIfCancellationRequested();
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
// update the UI
this.startButton.Enabled = true;
this.stopButton.Enabled = false;
this._cancelWork = null;
}
private void stopButton_Click(object sender, EventArgs e)
{
if (this._cancelWork != null)
this._cancelWork();
}
}
}
Updated, here's how to do the same without async/await:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication_22487698
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
IEnumerable<int> _data = Enumerable.Range(1, 100);
Action _cancelWork;
private void DoWorkItem(
int[] data,
int item,
CancellationToken token,
Action<int> progressReport,
ParallelLoopState loopState)
{
// observe cancellation
if (token.IsCancellationRequested)
{
loopState.Stop();
return;
}
// simulate a work item
Thread.Sleep(500);
// update progress
progressReport(item);
}
private void startButton_Click(object sender, EventArgs e)
{
// update the UI
this.startButton.Enabled = false;
this.stopButton.Enabled = true;
Action enableUI = () =>
{
// update the UI
this.startButton.Enabled = true;
this.stopButton.Enabled = false;
this._cancelWork = null;
};
Action<Exception> handleError = (ex) =>
{
// error reporting
MessageBox.Show(ex.Message);
};
try
{
// prepare to handle cancellation
var cts = new CancellationTokenSource();
var token = cts.Token;
this._cancelWork = () =>
{
this.stopButton.Enabled = false;
cts.Cancel();
};
var data = _data.ToArray();
var total = data.Length;
// prepare the progress updates
this.progressBar.Value = 0;
this.progressBar.Minimum = 0;
this.progressBar.Maximum = total;
var syncConext = SynchronizationContext.Current;
Action<int> progressReport = (i) =>
syncConext.Post(_ => this.progressBar.Increment(1), null);
// offload Parallel.For from the UI thread
// as a long-running operation
var task = Task.Factory.StartNew(() =>
{
Parallel.For(0, total, (item, loopState) =>
DoWorkItem(data, item, token, progressReport, loopState));
// observe cancellation
token.ThrowIfCancellationRequested();
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
task.ContinueWith(_ =>
{
try
{
task.Wait(); // rethrow any error
}
catch (Exception ex)
{
while (ex is AggregateException && ex.InnerException != null)
ex = ex.InnerException;
handleError(ex);
}
enableUI();
}, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (Exception ex)
{
handleError(ex);
enableUI();
}
}
private void stopButton_Click(object sender, EventArgs e)
{
if (this._cancelWork != null)
this._cancelWork();
}
}
}