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);
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.
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?
Consider a form with 2 buttons and a richtextbox:
public partial class MainForm : Form
{
CancellationTokenSource cts;
CancellationToken token;
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
cts.Dispose();
}
private void btnStart_Click(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
}
private void btnCancel_Click(object sender, EventArgs e)
{
try
{
cts.Cancel();
cts.Dispose();
}
catch (ObjectDisposedException exc)
{
MessageBox.Show(exc.GetType().Name);
//object disposed
}
}
public void WriteSomeLines()
{
if (ControlInvokeRequired(rtbLoops, () => rtbLoops.Text += "Starting new loop \r\n")) ;
else rtbLoops.Text += "Starting new loop \r\n";
for (int i = 0; i < 30; i++)
{
try
{
if (ControlInvokeRequired(rtbLoops, () => { rtbLoops.AppendText("New line " + i + "\r\n"); rtbLoops.ScrollToCaret(); })) ;
else rtbLoops.AppendText("New line " + i + "\r\n");
Thread.Sleep(250);
token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException ae)
{
MessageBox.Show(ae.GetType().Name);
return;
}
}
return;
}
public bool ControlInvokeRequired(Control c, Action a)
{
if (c.InvokeRequired)
c.Invoke(new MethodInvoker(delegate { a(); }));
else
return false;
return true;
}
}
Is there a difference if WriteSomeLines() is returning void and I use return inside, or if WriteSomeLines() returns Task and I do return null there? I read that I cannot use await with void returning methods, but inserting
await task;
after task declaration (in the code above) compiles perfectly fine, and runs with no issues.
Edit:
private async void btnStart_Click(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
await task;
rtbLoops.Text += "Task complete";
}
This compiles with no issues if WriteSomeLines() returns void.
Also, slightly unrealted, am I disposing CancellationTokenSource correctly here?
Second Edit:
So is this the correct approach:
private async void btnStart_Click(object sender, EventArgs e)
{
cts.Dispose();
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
bool result = await task;
if(result == true) rtbLoops.Text += "Task complete \r\n";
}
and
public async Task<bool> WriteSomeLines()
{
if (ControlInvokeRequired(rtbLoops, () => rtbLoops.Text += "Starting new loop \r\n")) ;
else rtbLoops.Text += "Starting new loop \r\n";
for (int i = 0; i < 30; i++)
{
try
{
if (ControlInvokeRequired(rtbLoops, () => { rtbLoops.AppendText("New line " + i + "\r\n"); rtbLoops.ScrollToCaret(); })) ;
else rtbLoops.AppendText("New line " + i + "\r\n");
await Task.Delay(250);
token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException ae)
{
MessageBox.Show(ae.GetType().Name);
return false;
}
}
return true;
You should never return a null task; that should cause a runtime NullReferenceException error.
You can use await within an async void method, but you cannot use await to consume an async void method (because you cannot await void).
I recommend that you review my async intro blog post; it should help you get a better understanding of async and await.
am I disposing CancellationTokenSource correctly here?
Your start button needs to cancel/dispose the old cts when it creates a new one.
I have the need to move some processes to async. I have 5 methods that I need to call individually and have run in the background so the user can continue on with their work.
The test code below seems to work... but I haven't been able to figure out how to return information (message) indicating that the a task has completed. The class will be called from a separate windows form so that the progress can be displayed....
from the form:
async void BtnGo_Click(object sender, System.EventArgs e)
{
label2.Text = #"Starting tasks...";
var progress = new Progress<string>(
p =>
{
label2.Text = p;
});
await TestTask.MyTestMain(progress);
}
the class:
public static class TestTask
{
public static Task MyTestMain(IProgress<string> pProgress)
{
return SomethingAsync(pProgress);
}
private static async Task SomethingAsync(IProgress<string> pProgress)
{
var t1 = SomeThing1(pProgress);
var t2 = SomeThing2(pProgress);
await Task.WhenAll(t1, t2);
if (pProgress != null) pProgress.Report(#"all tasks completed");
}
private static async Task SomeThing1()
{
await Task.Delay(9000);
var filename = #"c:\temp\tt1.txt";
if (File.Exists(filename))
File.Delete(filename);
using (TextWriter tw = new StreamWriter(filename))
{
await tw.WriteLineAsync(DateTime.Now.ToLongDateString());
}
if (pProgress != null) pProgress.Report(#"t1 completed");
}
private static async Task SomeThing2()
{
await Task.Delay(7000);
var filename = #"c:\temp\tt2.txt";
if (File.Exists(filename))
File.Delete(filename);
using (TextWriter tw = new StreamWriter(filename))
{
await tw.WriteLineAsync(DateTime.Now.ToLongDateString());
}
if (pProgress != null) pProgress.Report(#"t2 completed");
}
}
I would like know when each task has completed. Any help or direction would be appreciated.
EDIT
I have edited this post to reflect my changes... I still cannot get a progress report back to the UI... any thoughts?
You're doing IO bound work, you don't need to use thread-pool threads.
Transform your methods to use the async APIs of StreamWriter:
private static async Task FirstThingAsync()
{
var filename = #"c:\temp\tt1.txt";
if (File.Exists(filename))
File.Delete(filename);
using (TextWriter tw = new StreamWriter(filename))
{
await tw.WriteLineAsync(DateTime.Now);
}
}
Same for your second method, and then you can asynchronously wait on them concurrently:
private static async Task SomethingAsync()
{
var firstThing = FirstThingAsync();
var secondThing = SecondThingAsync();
await Task.WhenAll(firstThing, secondThing);
}
Edit:
You're never reaching your first Progress.Report call because your code is throwing an InvalidOperationException when you call t.Start() on a promise-style task:
t1.Start();
await t1;
t2.Start();
await t2;
The task returned from both method calls is a "hot task", meaning it's operation is already started. The docs on Task.Start say:
InvalidOperationException: The Task is not in a valid state to be
started. It may have already been started, executed, or canceled, or
it may have been created in a manner that doesn't support direct
scheduling.
The reason you're not seeing that exception is because you're swallowing it:
var t = SomethingAsync(pProgress);
When you don't await on the async operation. Your method calls should look like this:
public static Task MyTestMain(IProgress<string> pProgress)
{
return SomethingAsync(pProgress);
}
async void BtnGo_Click(object sender, System.EventArgs e)
{
label2.Text = #"Starting tasks...";
var progress = new Progress<string>(
p =>
{
label2.Text = p;
});
await TestTask.MyTestMain(progress);
}
I have a program that contains 2 listbox, this program is bassed to search file, and then compare with a StopWatch the difference to use AsyncAwait and TPL... The first listbox does the function using AsyncAwait (I don't know if it's the better way to do but it works, see my code below)
private async void button1_Click(object sender, EventArgs e)
{
Stopwatch stopWatch = new Stopwatch();
foreach (string d in Directory.GetDirectories(#"C:\Visual Studio Projectes\Hash\AsyncAwait\Carpetes"))
{
foreach (string s in Directory.GetFiles(d))
{
stopWatch.Start();
listBox1.Items.Add(s);
await Task.Delay(1);
btIniciar1.Enabled = false;
}
}
btIniciar1.Enabled = true;
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
textBox1.Text = ts.ToString("mm\\:ss\\.ff") + (" minuts");
}
And then in my second listbox is where I'm stuck, I don't know how to implement the Parallel.ForEach to act like async, what's the better way to do this? I can't find the way to use TPL in this case to do the same as my first listbox, could you help me please?
There's no point in using async in your example code, since it's not actually doing anything asynchronously. If you want to wrap the synchronous code in a background thread, use Task.Run.
Regarding Parallel.ForEach, you can treat it asynchronously by wrapping it in Task.Run: await Task.Run(() => Parallel.ForEach(...));
Note that parallel/background threads cannot directly access UI elements. You can use IProgress<T>/Progress<T> if you want to update the UI from a background/threadpool thread.
Update:
The serial code would look like:
private async void button1_Click(object sender, EventArgs e)
{
IProgress<string> progress = new Progress<string>(update =>
{
listBox1.Items.Add(s);
btIniciar1.Enabled = false;
});
var ts = await Task.Run(() =>
{
Stopwatch stopWatch = new Stopwatch();
foreach (string d in Directory.GetDirectories(#"C:\Visual Studio Projectes\Hash\AsyncAwait\Carpetes"))
{
foreach (string s in Directory.GetFiles(d))
{
stopWatch.Start();
progress.Report(s);
}
}
stopWatch.Stop();
return stopWatch.Elapsed;
});
btIniciar1.Enabled = true;
textBox1.Text = ts.ToString("mm\\:ss\\.ff") + (" minuts");
}
The parallel code would look like:
private async void button1_Click(object sender, EventArgs e)
{
IProgress<string> progress = new Progress<string>(update =>
{
listBox1.Items.Add(s);
btIniciar1.Enabled = false;
});
var ts = await Task.Run(() => Parallel.ForEach( ...
));
btIniciar1.Enabled = true;
textBox1.Text = ts.ToString("mm\\:ss\\.ff") + (" minuts");
}
Finally I've solved this issue doing this :
DirectoryInfo nodeDir = new DirectoryInfo(#"c:\files");
Parallel.ForEach(nodeDir.GetDirectories(), async dir =>
{
foreach (string s in Directory.GetFiles(dir.FullName))
{
Invoke(new MethodInvoker(delegate { lbxParallel.Items.Add(s); }));
contador++;
await Task.Delay(1);
}
}