how to display/report back progress of Task(Async) actions? - c#

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);
}

Related

Cant get Stored Procedure to run in parallel to logging task in C# Winform

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.

Converting threaded to multi threaded code? ( task factory ?)

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);

Writing to StorageFile concurrently

How can I write some text to a StorageFile concurrently in Windows Phone 8.1? I tried to implement mutex provided by lock in C#, but I'm unable to open the stream of the file that I want to write to under in the lock block.
This is what I'm doing:
StorageFile file = await folder.GetFileAsync(logFileName);
// Lock file access object and open filestream for writing.
lock (fileLockObject)
{
StreamWriter writer = new StreamWriter(await file.OpenStreamForWriteAsync());
writer.Write(sometext);
}
But lock doesn't allow any kind of async operation in it's body. How can I still maintain mutex and concurrency without using some third-party library like AsyncLock?
In async operations you need use AsyncLock instead lock
private readonly AsyncLock asyncLocker = new AsyncLock();
.....
using(await asyncLocker.LockAsync())
{
enter code here
}
AsyncLock msdn, github
Usually in this scenario you want the writes to be in order as much as possible and you want to offload the actual writing to another single thread. Below is a simplified example however in most cases you will want to optimize it to end and restart the thread when needed.
AutoResetEvent _waitHandle = new AutoResetEvent(false);
List<string> _writeCache = new List<string>();
bool _threadRunning = false;
void WriteToFile(string text)
{
lock (_writeCache)
{
_writeCache.Add(text);
_waitHandle.Set();
if(_threadRunning)
{
return;
}
_threadRunning = true;
}
Task.Run(async () =>
{
while (true)
{
_waitHandle.WaitOne();
StorageFile file = await folder.GetFileAsync(logFileName);
using (var f = await file.OpenStreamForWriteAsync())
{
using (StreamWriter writer = new StreamWriter(await file.OpenStreamForWriteAsync()))
{
lock (_writeCache)
{
while (_writeCache.Count > 0)
{
string s = _writeCache[0];
_writeCache.RemoveAt(0);
writer.Write(s);
}
}
}
}
}
});
}
I was able to resolve this using Semaphores. I used SlimSemaphore() to acquire and release a semaphore with a single resource before and writing to file. This enabled use of await operation and removed the limitations associated with lock and mutex as you can run await operations in the block (which mutex and lock don't really allow).
// define semaphore with only one resource to allocate.
SemaphoreSlim semaphore = new SemaphoreSlim(1);
// acquire semaphore
await semaphore.WaitAsync();
try { // write to file }
catch { // catch any exception }
finally
{
semaphore.Release();
}
Since you do need to use a lock statement, you could try:
using System.Threading.Tasks;
public async Task WriteToFile(string text)
{
StorageFile file = await folder.GetFileAsync(logFileName);
lock (fileLockObject)
{
Task<Stream> streamTask = file.OpenStreamForWriteAsync();
try
{
streamTask.Wait();
}
catch (AggregateException ex)
{
// You may want to handle errors here
}
if (!streamTask.IsFaulted)
{
using (StreamWriter writer = new StreamWriter(streamTask.Result))
{
writer.Write(text);
}
}
}
}
Usage (supposed this is an event handler, else use public async Task YourMethod(...)):
public async void YourMethod()
{
// 1. Blocks UI thread
Task t = WriteToFile("text");
t.Wait();
// or
// 2. Does not block UI thread
await WriteToFile("text");
}
Try this code
public static void WriteLog(string Error)
{
using (StreamWriter logfile = new StreamWriter(filePath + "Log.txt", true))
{
logfile.WriteLine(DateTime.Now.ToString() + ":" + DateTime.Now.Millisecond.ToString() + " -#: " + Error);
logfile.Close();
}
}

Modal Progress Form showing IProgress and supporting Cancellation of async Task for WinForms

I have been attempting to have a re-usable modal progress window (I.e. progressForm.ShowDialog()) to show progress from a running async task, including enabling cancellation.
I have seen some implementations that launch start the async task by hooking the Activated event handler on the form, but I need to start the task first, then show the modal dialog that will show it's progress, and then have the modal dialog close when completed or cancellation is completed (note - I want the form closed when cancellation is completed - signalled to close from the task continuation).
I currently have the following - and although this working - are there issues with this - or could this be done in a better way?
I did read that I need to run this CTRL-F5, without debugging (to avoid the AggregateException stopping the debugger in the continuation - and let it be caught in the try catch as in production code)
ProgressForm.cs
- Form with ProgressBar (progressBar1) and Button (btnCancel)
public partial class ProgressForm : Form
{
public ProgressForm()
{
InitializeComponent();
}
public event Action Cancelled;
private void btnCancel_Click(object sender, EventArgs e)
{
if (Cancelled != null) Cancelled();
}
public void UpdateProgress(int progressInfo)
{
this.progressBar1.Value = progressInfo;
}
}
Services.cs
- Class file containing logic consumed by WinForms app (as well as console app)
public class MyService
{
public async Task<bool> DoSomethingWithResult(
int arg, CancellationToken token, IProgress<int> progress)
{
// Note: arg value would normally be an
// object with meaningful input args (Request)
// un-quote this to test exception occuring.
//throw new Exception("Something bad happened.");
// Procressing would normally be several Async calls, such as ...
// reading a file (e.g. await ReadAsync)
// Then processing it (CPU instensive, await Task.Run),
// and then updating a database (await UpdateAsync)
// Just using Delay here to provide sample,
// using arg as delay, doing that 100 times.
for (int i = 0; i < 100; i++)
{
token.ThrowIfCancellationRequested();
await Task.Delay(arg);
progress.Report(i + 1);
}
// return value would be an object with meaningful results (Response)
return true;
}
}
MainForm.cs
- Form with Button (btnDo).
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private async void btnDo_Click(object sender, EventArgs e)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
// Create the ProgressForm, and hook up the cancellation to it.
ProgressForm progressForm = new ProgressForm();
progressForm.Cancelled += () => cts.Cancel();
// Create the progress reporter - and have it update
// the form directly (if form is valid (not disposed))
Action<int> progressHandlerAction = (progressInfo) =>
{
if (!progressForm.IsDisposed) // don't attempt to use disposed form
progressForm.UpdateProgress(progressInfo);
};
Progress<int> progress = new Progress<int>(progressHandlerAction);
// start the task, and continue back on UI thread to close ProgressForm
Task<bool> responseTask
= MyService.DoSomethingWithResultAsync(100, token, progress)
.ContinueWith(p =>
{
if (!progressForm.IsDisposed) // don't attempt to close disposed form
progressForm.Close();
return p.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
Debug.WriteLine("Before ShowDialog");
// only show progressForm if
if (!progressForm.IsDisposed) // don't attempt to use disposed form
progressForm.ShowDialog();
Debug.WriteLine("After ShowDialog");
bool response = false;
// await for the task to complete, get the response,
// and check for cancellation and exceptions
try
{
response = await responseTask;
MessageBox.Show("Result = " + response.ToString());
}
catch (AggregateException ae)
{
if (ae.InnerException is OperationCanceledException)
Debug.WriteLine("Cancelled");
else
{
StringBuilder sb = new StringBuilder();
foreach (var ie in ae.InnerExceptions)
{
sb.AppendLine(ie.Message);
}
MessageBox.Show(sb.ToString());
}
}
finally
{
// Do I need to double check the form is closed?
if (!progressForm.IsDisposed)
progressForm.Close();
}
}
}
Modified code - using TaskCompletionSource as recommended...
private async void btnDo_Click(object sender, EventArgs e)
{
bool? response = null;
string errorMessage = null;
using (CancellationTokenSource cts = new CancellationTokenSource())
{
using (ProgressForm2 progressForm = new ProgressForm2())
{
progressForm.Cancelled +=
() => cts.Cancel();
var dialogReadyTcs = new TaskCompletionSource<object>();
progressForm.Shown +=
(sX, eX) => dialogReadyTcs.TrySetResult(null);
var dialogTask = Task.Factory.StartNew(
() =>progressForm.ShowDialog(this),
cts.Token,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
await dialogReadyTcs.Task;
Progress<int> progress = new Progress<int>(
(progressInfo) => progressForm.UpdateProgress(progressInfo));
try
{
response = await MyService.DoSomethingWithResultAsync(50, cts.Token, progress);
}
catch (OperationCanceledException) { } // Cancelled
catch (Exception ex)
{
errorMessage = ex.Message;
}
finally
{
progressForm.Close();
}
await dialogTask;
}
}
if (response != null) // Success - have valid response
MessageBox.Show("MainForm: Result = " + response.ToString());
else // Faulted
if (errorMessage != null) MessageBox.Show(errorMessage);
}
I think the biggest issue I have, is that using await (instead of
ContinueWith) means I can't use ShowDialog because both are blocking
calls. If I call ShowDialog first the code is blocked at that point,
and the progress form needs to actually start the async method (which
is what I want to avoid). If I call await
MyService.DoSomethingWithResultAsync first, then this blocks and I
can't then show my progress form.
The ShowDialog is indeed a blocking API in the sense it doesn't return until the dialog has been closed. But it is non-blocking in the sense it continues to pump messages, albeit on a new nested message loop. We can utilize this behavior with async/await and TaskCompletionSource:
private async void btnDo_Click(object sender, EventArgs e)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
// Create the ProgressForm, and hook up the cancellation to it.
ProgressForm progressForm = new ProgressForm();
progressForm.Cancelled += () => cts.Cancel();
var dialogReadyTcs = new TaskCompletionSource<object>();
progressForm.Load += (sX, eX) => dialogReadyTcs.TrySetResult(true);
// show the dialog asynchronousy
var dialogTask = Task.Factory.StartNew(
() => progressForm.ShowDialog(),
token,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
// await to make sure the dialog is ready
await dialogReadyTcs.Task;
// continue on a new nested message loop,
// which has been started by progressForm.ShowDialog()
// Create the progress reporter - and have it update
// the form directly (if form is valid (not disposed))
Action<int> progressHandlerAction = (progressInfo) =>
{
if (!progressForm.IsDisposed) // don't attempt to use disposed form
progressForm.UpdateProgress(progressInfo);
};
Progress<int> progress = new Progress<int>(progressHandlerAction);
try
{
// await the worker task
var taskResult = await MyService.DoSomethingWithResultAsync(100, token, progress);
}
catch (Exception ex)
{
while (ex is AggregateException)
ex = ex.InnerException;
if (!(ex is OperationCanceledException))
MessageBox.Show(ex.Message); // report the error
}
if (!progressForm.IsDisposed && progressForm.Visible)
progressForm.Close();
// this make sure showDialog returns and the nested message loop is over
await dialogTask;
}

How to chain a task to a previous instance of it?

Looking to chain a task to a previous instance if it exists. Currently, both are executed at the same time.
Initial code that works for one task :
private async void MenuMediaAddFiles_OnClick(object sender, RoutedEventArgs e)
{
var dialog = GetDefaultOpenFileDialog();
using (dialog)
{
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
using (var progress = new SimpleProgress(this))
{
int addFiles = await _context.AddFiles(dialog.FileNames, progress);
Console.WriteLine("Files added: {0}", addFiles);
}
}
}
}
A failed attempt to make it work :
Task<int> _files;
private async void MenuMediaAddFiles_OnClick(object sender, RoutedEventArgs e)
{
var dialog = GetDefaultOpenFileDialog();
using (dialog)
{
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
using (var progress = new SimpleProgress(this))
{
int addFiles;
Task<int> files = _context.AddFiles(dialog.FileNames, progress);
if (_files == null)
{
_files = files;
}
else
{
var task1 = await _files.ContinueWith(task => _context.AddFiles(dialog.FileNames, new SimpleProgress(this)));
}
addFiles = await _files;
Console.WriteLine("Files added: {0}", addFiles);
}
}
}
}
You were pretty close, but there were a few things that needed to be modified:
private Task<int> previousTask = Task.FromResult(0);
private async void MenuMediaAddFiles_OnClick(object sender, RoutedEventArgs e)
{
var dialog = GetDefaultOpenFileDialog();
using (dialog)
{
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
using (var progress = new SimpleProgress(this))
{
previousTask = previousTask.ContinueWith(t =>
_context.AddFiles(dialog.FileNames, progress))
.UnWrap(); ;
int addFiles = await previousTask;
Console.WriteLine("Files added: {0}", addFiles);
}
}
}
}
Things to note:
Rather than having the previous task be null sometimes, it was easier to initialize it to an already completed task (Task.FromResult(0)). This avoids the null check code.
You were calling AddFiles twice. You shouldn't have been calling it before the if, and you weren't ever assigning the task to the instance field inside the if.
I used UnWrap instead of await to turn the Task<Task<int>> into a Task<int>. Both work, but in this case I felt UnWrap made its intentions clearer.
Note that since the entire event handler will be running in the UI thread there's no need to synchronize access to previousTask, if it doesn't, you'd need to do some locking.

Categories

Resources