This is my function that received List of files and do my stuff:
private IEnumerable<string> _source;
public void doWork()
{
ParallelLoopState pls = new ParallelLoopState();
pls.LowestBreakIteration = 5;
_tokenSource = new CancellationTokenSource();
var token = _tokenSource.Token;
Task.Factory.StartNew(() =>
{
try
{
Parallel.ForEach(_source,
new ParallelOptions
{
MaxDegreeOfParallelism = _parallelThreads //limit number of parallel threads
},
file =>
{
if (token.IsCancellationRequested)
return;
//do work...
});
}
catch (Exception)
{ }
}, _tokenSource.Token).ContinueWith(
t =>
{
//finish...
}
, TaskScheduler.FromCurrentSynchronizationContext() //to ContinueWith (update UI) from UI thread
);
}
I saw that this ForEach can receive ParallelOptions add i wonder how to use it in order to run this operation many times.
Related
I'm writing a simple producer/consumer application, but I'm noticing a really strange behaviour..This is the code:
private Thread _timelineThread = null;
private BufferBlock<RtpPacket> _queue = null;
private AutoResetEvent _stopping = new AutoResetEvent(false);
static void Main(string[] args)
{
// Start consumer thread
Consume();
// Produce
var t = new Thread(() =>
{
while (true)
{
var packet = RtpPacket.GetNext();
_queue.Post(packet);
Thread.Sleep(70);
}
}
t.Join();
}
static void Consume()
{
_timelineThread = new Thread(async () =>
{
while (_stopping.WaitOne(0) == false)
{
// Start consuming...
while (await _queue.OutputAvailableAsync())
{
var packet = await _queue.ReceiveAsync();
// Some processing...
}
}
});
_timelineThread.Start();
}
This is intended to be an infinite loop (until I route the _stopping signal). But, when _timelineThread hits the first await _queue.OutputAvailableAsync(), the thread changes state to 'Stopped'. There is something wrong that I'm not considering ?
If I change the Consume() function to this:
static void Consume()
{
_timelineThread = new Thread(() =>
{
while (_stopping.WaitOne(0) == false)
{
// Start consuming...
while (_queue.OutputAvailableAsync().GetAwaiter().GetResult())
{
var packet = _queue.ReceiveAsync().GetAwaiter().GetResult();
// Some processing...
}
}
});
_timelineThread.Start();
}
the thread runs without any problem..but the code is almost identical to the previous one..
EDIT: after one hour also this 'hack' doesn't seems to work..thread is 'Running' but I don't receive any data from the queue..
The Thread constructor does not understand async delegates. You can read about this here:
Is it OK to use "async" with a ThreadStart method?
Async thread body loop, It just works, but how?
My suggestion is to use a synchronous BlockingCollection<RtpPacket> instead of the BufferBlock<RtpPacket>, and consume it by enumerating the GetConsumingEnumerable method:
var _queue = new BlockingCollection<RtpPacket>();
var producer = new Thread(() =>
{
while (true)
{
var packet = RtpPacket.GetNext();
if (packet == null) { _queue.CompleteAdding(); break; }
_queue.Add(packet);
Thread.Sleep(70);
}
});
var consumer = new Thread(() =>
{
foreach (var packet in _queue.GetConsumingEnumerable())
{
// Some processing...
}
});
producer.Start();
consumer.Start();
producer.Join();
consumer.Join();
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?
I have a multi-threaded application and i'm using this to control the no.of processes (2). I want to process files only for specified time duration. Below is my approach. I'm getting The CancellationTokenSource has been disposed. error.
If i'm not dispoing the cts.Dispose(); then the process is not stooping after 10 sec. It is keep on processing till 1000. Can any one help me here.
Note: I've a 1000 files. Requirement is process files with in a given time (10 sec) by controlling the number of process (2) and sleep in between (some x ms).
Below is my code
class Program
{
static void Main(string[] args)
{
try
{
LimitedConcurrencyLevelTaskScheduler lcts = new LimitedConcurrencyLevelTaskScheduler(2);
List<Task> tasks = new List<Task>();
TaskFactory factory = new TaskFactory(lcts);
CancellationTokenSource cts = new CancellationTokenSource();
for (int i = 0; i < 1000; i++)
{
int i1 = i;
var t = factory.StartNew(() =>
{
if (cts != null)
Console.WriteLine("{0} --- {1}", i1, GetGuid(cts.Token));
}, cts.Token);
tasks.Add(t);
}
Task.WaitAll(tasks.ToArray(), 10000, cts.Token);
cts.Dispose();
Console.WriteLine("\n\nSuccessful completion.");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private static Guid GetGuid(CancellationToken cancelToken)
{
if (cancelToken.IsCancellationRequested)
{
return Guid.Empty;
}
Thread.Sleep(TimeSpan.FromSeconds(1));
return Guid.NewGuid();
}
}
What you can do is you can run a Task that will change your Cancellation Token state to canceled after some time.
Like this :
class Program
{
public static void ProcessFiles(CancellationToken cts)
{
try
{
LimitedConcurrencyLevelTaskScheduler lcts = new LimitedConcurrencyLevelTaskScheduler(2);
List<Task> tasks = new List<Task>();
TaskFactory factory = new TaskFactory(lcts);
for (int i = 0; i < 1000; i++)
{
int i1 = i;
var t = factory.StartNew(() =>
{
if (cts != null) Console.WriteLine("{0} --- {1}", i1, GetGuid());
}, cts);
tasks.Add(t);
}
Task.WaitAll(tasks.ToArray());
Console.WriteLine("\n\nSuccessful completion.");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
Task.Factory.StartNew(() => { Thread.Sleep(10000); cts.Cancel(); });
ProcessFiles(cts.Token);
Console.ReadKey();
}
private static Guid GetGuid()
{
Thread.Sleep(TimeSpan.FromSeconds(1));
return Guid.NewGuid();
}
}
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();
}
}
}
I am opening n concurrent threads in my function:
List<string> _files = new List<string>();
public void Start()
{
CancellationTokenSource _tokenSource = new CancellationTokenSource();
var token = _tokenSource.Token;
Task.Factory.StartNew(() =>
{
try
{
Parallel.ForEach(_files,
new ParallelOptions
{
MaxDegreeOfParallelism = 5 //limit number of parallel threads
},
file =>
{
if (token.IsCancellationRequested)
return;
//do work...
});
}
catch (Exception)
{ }
}, _tokenSource.Token).ContinueWith(
t =>
{
//finish...
}
, TaskScheduler.FromCurrentSynchronizationContext() //to ContinueWith (update UI) from UI thread
);
}
there is a way to know while this function still processing that 1 file finish ? i am now talking about ContinueWith which this is the case after all my list has finished.
Not sure I perfectly understand your issue but you could use some standard notification through a method:
public void Start()
{
CancellationTokenSource _tokenSource = new CancellationTokenSource();
var token = _tokenSource.Token;
Task.Factory.StartNew(() =>
{
try
{
Parallel.ForEach(_files,
new ParallelOptions
{
MaxDegreeOfParallelism = 5 //limit number of parallel threads
},
file =>
{
if (token.IsCancellationRequested)
return;
//do work...
OnDone(file);
});
}
catch (Exception)
{ }
}, _tokenSource.Token).ContinueWith(
t =>
{
//finish...
}
, TaskScheduler.FromCurrentSynchronizationContext() //to ContinueWith (update UI) from UI thread
);
}
public void OnDone(string fileName)
{
// Update the UI, assuming you're using WPF
someUIComponent.Dispatcher.BeginInvoke(...)
}
You may need additional locks if you update some shared state, but to only update the UI (e.g. a row in a datagrid or an element in a list) should be fine because the synchronization is enforced by the dispatcher invocation.