Given the following code, modified from Stephen Toub's article.
http://blogs.msdn.com/b/pfxteam/archive/2011/12/15/10248293.aspx
public async Task Start(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
await this.acceptCount.WaitAsync(token).ConfigureAwait(false);
if (token.IsCancellationRequested)
break;
var args = new SocketAsyncEventArgs();
args.UserToken = new SocketFrame
{
CancellationToken = token,
Buffer = null,
Message = null,
};
// How do I manage this additional task?
args.Completed += (s, e) => this.AcceptInbound((Socket)s, e).Wait(token);
if (!this.socket.AcceptAsync(args))
await this.AcceptInbound(this.socket, args);
}
}
private async Task AcceptInbound(Socket sender, SocketAsyncEventArgs e)
{
var frame = (SocketFrame)e.UserToken;
this.acceptCount.Release();
Socket connectedClient = e.AcceptSocket;
var args = new SocketAsyncEventArgs();
args.SetBuffer(new byte[0x1000], 0, 0x1000);
var awaitable = new SocketAwaitable(args);
while (!frame.CancellationToken.IsCancellationRequested)
{
await connectedClient.ReceiveAsync(awaitable);
var bytesRead = args.BytesTransferred;
if (bytesRead <= 0)
break;
if (this.AppendByteArrayToFrame(frame, args.Buffer, bytesRead))
this.reader.MessageReceived(frame.Message);
}
}
How do I avoid the Wait on the args.Completed event?
I want exceptions raised in AcceptInbound to bubble up, so I really don't want to wait there.
What I am trying to achieve is to bind the AcceptInbound task to the current task, so that when I wait on the current task, the exceptions raised are caught.
You can register an async event handler (which is the only place async void is appropriate). That allows you to await instead of blocking with Wait:
args.Completed += async (s, e) => await AcceptInbound((Socket)s, e);
If instead you want Start to handle completion and exceptions from all these tasks then I would store them all until Start completes and then use Task.WhenAll to make sure all operations completed and rethrow any exception they may produced:
public async Task Start(CancellationToken token)
{
var tasks = new ConcurrentBag<Task>();
while (!token.IsCancellationRequested)
{
// ...
args.Completed += (s, e) => tasks.Add(AcceptInbound((Socket)s, e));
// ...
}
await Task.WhenAll(tasks);
}
TaskCompletionSource to the rescue!
Take a look at this:
private TaskCompletionSource<Something> _tcs = new TaskCompletionSource<Something>();
public void FinishAwait(Something result) {
_tcs.SetResult(result);
}
public void FailAwait(Exception exception) {
_tcs.SetException(exception);
}
public async Task<Something> M() {
var result = await _tcs.Task;
return result;
}
This a very customised way of controlling when and how a particular Task completes.
You could use that to await a custom built task which is manually triggered by the completion of the AcceptAsync operation's event handler.
In your case, the generic argument T can be object because we only needed it to be void and object is the most benign of all types.
You could then await that intermediate task inside the Start method, just before awaiting the AcceptInbound method's Task.
Your problem was not that Exceptions are not passed from
callee to caller via await call but rather that you were
calling AcceptIncomming too soon.
Simply receiving true in the call to AcceptAsync doesn't mean that the "Accept occurred" but rather that is successfully started.
Take a look at that part of the code:
public async Task Start(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
await this.acceptCount.WaitAsync(token).ConfigureAwait(false);
if (token.IsCancellationRequested)
break;
var args = new SocketAsyncEventArgs();
args.UserToken = new SocketFrame
{
CancellationToken = token,
Buffer = null,
Message = null,
};
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
// How do I manage this additional task?
args.Completed += (s, e) => {
switch (e.SocketError) {
case SocketError.Success:
tcs.SetResult(null);
break;
default:
tcs.SetException(new SocketException());
break;
}
};
if (!this.socket.AcceptAsync(args)) {
// receiving a true value here
// only means that AcceptAsync
// started successfully
// we still need to
// "await for the Completed event handler"
await tcs.Task; // this will automatically throw
// if tcs.SetException(...) was used
// also, it will return null if all is well
// but most importantly, it will allow us to
// know when to call the next method
await this.AcceptInbound(this.socket, args);
}
}
}
Related
I have a button click event handler in which I need to have 3 sec delay to make some flag true ..so it takes time to completely execute the function now meantime if the user click on the button again then this is making flag true for second click also...so I want to cancel the first click event as soon as I receive another click request.
This is my code :
private async void ClickEventHandler(ClickEvent obj)
{
int indexOfSelectedItem = this.List.IndexOf(this.List.FirstOrDefault(x => Convert.ToDouble(x.Value) == obj.Value));
if (indexOfSelectedItem > -1)
{
for (int i = 0; i < indexOfSelectedItem; i++)
{
var item = this.List.ElementAtOrDefault(0);
this.List.RemoveAt(0);
this.List.Add(item);
}
this.IsScrollEnabled = false;
await Task.Delay(3000);
this.IsScrollEnabled = true;
}
}
Yes I need to cancel the execution of the first method and call the method again ..so that it will wait for 3 sec after clicking it on second time
A simple example with a cancellation token:
private CancellationTokenSource tokenSource = new();
private async void ClickEventHandler(ClickEvent obj)
{
// Since this method is called from the clicker,
// it always starts on the main thread. Therefore,
// there is no need for additional Thread-Safe.
tokenSource.Cancel();
tokenSource = new();
CancellationToken token = tokenSource.Token;
int indexOfSelectedItem = this.List.IndexOf(this.List.FirstOrDefault(x => Convert.ToDouble(x.Value) == obj.Value));
if (indexOfSelectedItem > -1)
{
for (int i = 0; i < indexOfSelectedItem; i++)
{
var item = this.List.ElementAtOrDefault(0);
this.List.RemoveAt(0);
this.List.Add(item);
}
this.IsScrollEnabled = false;
try
{
// If the cancellation is during the Delay, then an exception will be exited.
await Task.Delay(3000, token);
this.IsScrollEnabled = true;
}
catch (Exception)
{
// Here, if necessary, actions in case the method is canceled.
}
}
}
P.S. In the example, the token is checked only in the Delay(...) method. If you need to check somewhere else, then insert a call to the method token.ThrowIfCancellationRequested(); into this place.
you could change a variable when the button gets clicked and change it back after it is done and then add an if statement checking the variable
As like xceing said , you can have a variable to check if already clicked and yet to complete the process.
Here is a sample
//keep a variable to check already clicked and yet to complete the action
bool clickEventInProgress = false;
private async void ClickEventHandler(ClickEvent obj)
{
//check it before start processing click action
if (!clickEventInProgress)
{
clickEventInProgress = true;
int indexOfSelectedItem = this.List.IndexOf(this.List.FirstOrDefault(x => Convert.ToDouble(x.Value) == obj.Value));
if (indexOfSelectedItem > -1)
{
for (int i = 0; i < indexOfSelectedItem; i++)
{
var item = this.List.ElementAtOrDefault(0);
this.List.RemoveAt(0);
this.List.Add(item);
}
this.IsScrollEnabled = false;
await Task.Delay(3000);
this.IsScrollEnabled = true;
}
clickEventInProgress = false;
}
}
you can make the variable false, one the process completed . So that next click operation will work fine.
It's not clear what exactly you are doing. A general solution could execute the cancellable task using Task.Run and then cancel it using a CancellationTokenSource. It's important to pass the associated CancellationToken to the Task API (and any asynchronous API that supports cancellation in general) too in order to enable full cancellation support e.g. cancellation of Task.Delay:
MainWindow.xaml
<Window>
<Button Content="Go!"
Click="OnClick" />
</Window>
MainWindow.xaml.cs
partial class MainWindow : Window
{
private CancellationTokenSource CancellationTokenSource { get; set; }
private SemaphoreSlim Semaphore { get; } = new SemaphoreSlim(1, 1);
public MainWindow() => InitializeComponent();
private async void OnClick(object sender, RoutedEventArgs e)
{
// If there is nothing to cancel, the reference is NULL
this.CancellationTokenSource?.Cancel();
// Wait for the previous operation to be cancelled.
// If there is nothing to cancel the SemaphoreSlim has a free slot
// and the execution continues.
await this.Semaphore.WaitAsync();
try
{
using (this.CancellationTokenSource = new CancellationTokenSource())
{
await RunCancellableOperationAsync(this.CancellationTokenSource.Token);
}
}
catch (OperationCanceledException)
{
// Invalidate disposed object to make it unusable
this.CancellationTokenSource = null;
}
finally // Cancellation completed
{
this.Semaphore.Release();
}
}
private async Task RunCancellableOperationAsync(CancellationToken cancellationToken)
{
// Execute blocking code concurrently to enable cancellation
await Task.Run(() =>
{
for (int index = 0; index < 1000; index++)
{
// Abort the iteration if requested
cancellationToken.ThrowIfCancellationRequested();
// Simulate do something
Thread.Sleep(5000);
}
}, cancellationToken);
// Support cancellation of the delay
await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken);
}
}
I have two instances of a class which creates a UDP socket to receive data from UDP clients. If one of the instances throws an exception I want to handle it immediately in a higher layer. In my program they're started with await Task.WhenAll(recv1.StartAsync(), recv2.StartAsync). This however waits for all tasks to finish before the first exception is thrown. Any ideas on how to resolve this problem?
static async Task Main(string[] args)
{
var udpReceiver1 = new UdpReceiver(localEndpoint1);
var udpReceiver2 = new UdpReceiver(localEndpoint2);
var cts = new CancellationTokenSource();
try
{
await Task.WhenAll(udpReceiver1.StartAsync(cts.Token), udpReceiver2.StartAsync(cts.Token));
}
catch (Exception e)
{
// Handle Exception...
cts.Cancel();
}
}
class UdpReceiver
{
public UdpReceiver(IPEndPoint endpoint)
{
udpClient = new UdpClient(endpoint);
}
public async Task StartAsync(CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
var result = await ReceiveAsync(cancellationToken);
var message = Encoding.UTF8.GetString(result.Buffer);
Trace.WriteLine($"UdpClient1 received message:{Encoding.UTF8.GetString(result.Buffer)}");
// throw new Exception("UdpClient1 raising exception");
}
}
}
private async Task<UdpReceiveResult> ReceiveAsync(CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<UdpReceiveResult>();
using (cancellationToken.Register(() => tcs.TrySetCanceled(), false))
{
var task = udpClient.ReceiveAsync();
var completedTask = await Task.WhenAny(task, tcs.Task);
var result = await completedTask.ConfigureAwait(false);
return result;
}
}
private UdpClient udpClient;
}
Update 1: Task.WhenAny would be a viable solution. Thanks #CamiloTerevinto
try
{
await await Task.WhenAny(udpReceiver1.StartAsync(cts.Token), udpReceiver2.StartAsync(cts.Token));
}
catch (Exception e)
{
// Handle Exception...
cts.Cancel();
}
Update 2: For a more fine grained exception handling of all tasks I'd go with my own adapted implementation of Task.WhenAll proposed by #Servy.
The behavior is different enough from the framework WhenAll implementation that you're probably best just writing your own adapted version, fortunately it's not particularly hard to implement. Just attach a continuation to every single task, if any is cancelled or faulted, the resulting task does the same, if it succeeds store the result, and if the last task was the one to succeed, complete the task with all of the stored results.
public static Task<IEnumerable<TResult>> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks)
{
var listOfTasks = tasks.ToList();
if (listOfTasks.Count == 0)
{
return Task.FromResult(Enumerable.Empty<TResult>());
}
var tcs = new TaskCompletionSource<IEnumerable<TResult>>();
var results = new TResult[listOfTasks.Count];
int completedTasks = 0;
for (int i = 0; i < listOfTasks.Count; i++)
{
int taskIndex = i;
Task<TResult> task = listOfTasks[i];
task.ContinueWith(_ =>
{
if (task.IsCanceled)
tcs.TrySetCanceled();
else if (task.IsFaulted)
tcs.TrySetException(task.Exception.InnerExceptions);
else
{
results[taskIndex] = task.Result;
if (Interlocked.Increment(ref completedTasks) == listOfTasks.Count)
{
tcs.TrySetResult(results);
}
}
});
}
return tcs.Task;
}
As with so many task based generic operations, you also need a version without the results, and if you don't want to deal with a notable overhead, you really need to just copy-paste the result-based approach but with all of the results ripped out, which isn't hard, just inelegant. Turning all of these tasks into tasks with a result would also work, but for an operation like this the overhead is likely problematic.
public static Task WhenAll(IEnumerable<Task> tasks)
{
var listOfTasks = tasks.ToList();
if (listOfTasks.Count == 0)
{
return Task.CompletedTask;
}
var tcs = new TaskCompletionSource<bool>();
int completedTasks = 0;
for (int i = 0; i < listOfTasks.Count; i++)
{
int taskIndex = i;
Task task = listOfTasks[i];
task.ContinueWith(_ =>
{
if (task.IsCanceled)
tcs.TrySetCanceled();
else if (task.IsFaulted)
tcs.TrySetException(task.Exception.InnerExceptions);
else
{
if (Interlocked.Increment(ref completedTasks) == listOfTasks.Count)
{
tcs.TrySetResult(true);
}
}
});
}
return tcs.Task;
}
There's probably a way to do it but I can't think of one without making your code very messy. It'd be better to handle the exception in the actual task. If you need to handle it with common code, use a handler delegate.
static async Task Main(string[] args)
{
var cts = new CancellationTokenSource();
//This is our common error handler
void HandleException(Exception ex)
{
Log("Exception!" + ex.Message);
cts.Cancel();
}
var udpReceiver1 = new UdpReceiver(localEndpoint1);
var udpReceiver2 = new UdpReceiver(localEndpoint1);
//We pass the handler as one of the arguments
await Task.WhenAll(udpReceiver1.StartAsync(cts.Token, HandleException), udpReceiver2.StartAsync(cts.Token, HandleException));
}
class UdpReceiver
{
public async Task StartAsync(CancellationToken cancellationToken, Action<Exception> errorHandler)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
//Main logic goes here
}
}
catch(Exception ex)
{
errorHandler(ex); //Call common error handling code
}
}
You could await the tasks in two steps. In the first step await any of them to complete, and in case of failure initiate the cancellation. Don't handle the exception in this step. Delay the exception handling until the second step, after awaiting all of the tasks to complete. Both tasks may have failed, so you may want to handle the exception of each one separately.
Task task1 = udpReceiver1.StartAsync(cts.Token);
Task task2 = udpReceiver2.StartAsync(cts.Token);
// Await any task to complete
Task firstCompletedTask = await Task.WhenAny(task1, task2);
if (firstCompletedTask.IsFaulted) cts.Cancel();
try
{
// Await them all to complete
await Task.WhenAll(task1, task2);
}
catch
{
if (task1.IsFaulted) HandleException(task1.Exception.InnerException);
if (task2.IsFaulted) HandleException(task2.Exception.InnerException);
}
I am attempting to use the TPL in a windows service for efficient asynchronous processing. The service runs in an infinite loop, until the service is cancelled.
Here is the code that I'm using for the main service methods:
private CancellationTokenSource cancellationTokenSource;
private readonly List<Task> tasks = new List<Task>();
protected override void OnStart(string[] args)
{
cancellationTokenSource = new CancellationTokenSource();
tasks.Add(Task.Factory.StartNew(() =>
{
Worker(cancellationTokenSource.Token);
}, cancellationTokenSource.Token));
}
private async void Worker(CancellationToken token)
{
bool keepGoing = true;
while (keepGoing)
{
try
{
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
}
//Parallel.ForEach(processors, processor =>
//{
await processor.Process();
//});
}
catch (Exception ex)
{
if (ex is OperationCanceledException)
{
keepGoing = false;
}
else
{
//write log here
}
}
finally
{
await Task.Delay(configurationSettings.OperationSettings.ServiceOperationDelay, token).ContinueWith(tsk => { });
}
}
}
protected override void OnStop()
{
cancellationTokenSource.Cancel();
using var mres = new ManualResetEventSlim();
using (cancellationTokenSource.Token.Register(() => mres.Set()))
{
Task.Factory.ContinueWhenAll(tasks.ToArray(), (t) => mres.Set());
mres.Wait();
}
}
The call to the processor basically does the following:
var records = await interfaceService.Get()
foreach record retrieved
await interfaceService.Patch()
The service utilizes an HttpClient instance to make requests.
**Get:**
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get,
"");
using HttpResponseMessage response = await httpClient.SendAsync(request);
if (response.IsSuccessStatusCode)
{
//return results
}
else
{
throw Exception("Foo bar")
}
**Patch**
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Patch, "")
{
Content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json")
};
using HttpResponseMessage response = await httpClient.SendAsync(request);
The issue that I am encountering is that if the endpoint becomes unavailable, the service just doesn't effectively catch any exception or responses returned and for a lack of better terminology, falls in a hole. I believe my issue is with the way that the tasks are being managed.
What I want to ultimately be able to do is have the service with each iteration of the loop
Fire off specific tasks asynchronously, which perform the get/patch operation, at once
Wait until all are completed.
Log results of each to a file
Go to sleep
Start at step #1
In addition, when the service stops, I want to gracefully stop processing of each task.
Any help with this is greatly appreciated!
Hello guys I'm pretty new to the whole async stuff and it would be nice if you could give me some advice. I'm not really sure if my approach is OK.
Lets say I have a bus device that is reading data and it fires an event if a telegram is completed. Now I want to check each telegram for its length. If length == expectation -> OK if not try until is OK or timeout. But it want to check for length 1, 2 and 5 at the same time.
UPDATE:
OK I changed my example to an async approach but I still can't figure out how this should help me with my problem? OK on the plus side I don't have threads anymore that are blocked most of the time, but this wasn't my problem :(
So I try to explain in a different way. I want a async method that listens on the bus and returns the telegram that match the defined length
async Task<byte[]> GetTelegramAsync(int length, Timespan timeout)
I want to do something like this
Task<byte[]> t1 = GetTelegramAsync(1);
Task<byte[]> t2 = GetTelegramAsync(6);
Task<byte[]> t4 = GetTelegramAsync(4);
Task t4 = DoOtherStuffAsync();
DoStuff();
Task.WaitAll(AsyncRsp(t1), AsyncRsp(t2), AsyncRsp(t3), t4);
/* Output
Get telegram with length of 1
Get telegram with length of 6
Get telegram with length of 4
Start doing other async stuff
Sync stuff done...
Telegram found 0x00 0x01 0x02 0x03 0x04 0x05
Async stuff done...
Telegram found 0xFF
Telegram with length 4 not found
*/
Here is my first BusDevice class. A thread starts that listens on the bus, if a telegram is received an event fires.
class BusDeviceThread
{
private readonly Random _r = new Random();
private Thread _t;
public event EventHandler<TelegramReceivedArgs> TelegramReceived;
public void Connect()
{
_t = new Thread(FetchBusData)
{
Name = "FetchBusData",
Priority = ThreadPriority.Normal
};
_t.Start();
}
public void Close()
{
_t.Abort();
_t.Join();
}
private void FetchBusData()
{
while (true)
{
Thread.Sleep(_r.Next(100, 1000));
var buffer = new byte[_r.Next(1, 10)];
_r.NextBytes(buffer);
OnTelegramReceived(new TelegramReceivedArgs(buffer));
}
}
private void OnTelegramReceived(TelegramReceivedArgs e)
{
var handler = TelegramReceived;
if (handler != null) handler(this, e);
}
}
And here is the changed BusDevice class utilizing async await.
class BusDeviceAsync
{
private readonly Random _r = new Random();
public event EventHandler<TelegramReceivedArgs> TelegramReceived;
public async Task Connect(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var telegram = await FetchBusData();
OnTelegramReceived(new TelegramReceivedArgs(telegram.ToArray()));
}
}
private async Task<IEnumerable<byte>> FetchBusData()
{
await Task.Delay(_r.Next(100, 1000));
var buffer = new byte[_r.Next(1, 10)];
_r.NextBytes(buffer);
return buffer;
}
private void OnTelegramReceived(TelegramReceivedArgs e)
{
var handler = TelegramReceived;
if (handler != null) handler(this, e);
}
}
Like I said it doesn't help me with my problem, the
async Task<byte[]> GetTelegramAsync(int length, Timespan timeout)
implementation stays the same or do I miss a point here?
byte[] GetTelegram(int length, TimeSpan timeout)
{
byte[] telegram = null;
using (var resetEvent = new AutoResetEvent(false))
{
EventHandler<TelegramReceivedArgs> handler = (sender, e) =>
{
var t = e.Telegram;
if (Check(t, length))
{
telegram = t;
resetEvent.Set();
}
};
_d.TelegramReceived += handler;
resetEvent.WaitOne(timeout.Milliseconds);
_d.TelegramReceived -= handler;
}
return telegram ?? new byte[0];
}
async Task<byte[]> GetTelegramAsync(int length, TimeSpan timeout)
{
return await Task.Run(() => GetTelegram(length, timeout));
}
I updated my example, but I can't figure out the difference regarding
my problem. Well I certainly have fixed the blocked thread "problem".
This is not exactly what I meant, you're still using the pull model for your data (now with help of Task.Delay), not the push model (where the notification is coming asynchronously from the bus driver, as shown here).
Anyway, I think the following implementation might be what you're looking for. Note it doesn't explicitly use threads at all, beside for async I/O bus read simulation. Substitute the real device APM API for readBus:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
class Program
{
public class TelegramEventArg: EventArgs
{
public byte[] Data { get; set; }
}
public EventHandler<TelegramEventArg> TelegramEvent = delegate { };
async Task<byte[]> ReadTelegramAsync(int size, CancellationToken token)
{
var tcs = new TaskCompletionSource<byte[]>();
EventHandler<TelegramEventArg> handler = null;
bool subscribed = false;
handler = (s, e) =>
{
if (e.Data.Length == size)
{
this.TelegramEvent -= handler;
subscribed = false;
tcs.TrySetResult(e.Data);
}
};
this.TelegramEvent += handler;
try
{
subscribed = true;
using (token.Register(() => tcs.TrySetCanceled()))
{
await tcs.Task.ConfigureAwait(false);
return tcs.Task.Result;
}
}
finally
{
if (subscribed)
this.TelegramEvent -= handler;
}
}
async Task ReadBusAsync(CancellationToken token)
{
while (true)
{
// get data from the bus
var data = await Task.Factory.FromAsync(
(asyncCallback, asyncState) =>
readBus.BeginInvoke(asyncCallback, asyncState),
(asyncResult) =>
readBus.EndInvoke(asyncResult),
state: null).ConfigureAwait(false);
token.ThrowIfCancellationRequested();
this.TelegramEvent(this, new TelegramEventArg { Data = data });
}
}
// simulate the async bus driver with BeginXXX/EndXXX APM API
static readonly Func<byte[]> readBus = () =>
{
var random = new Random(Environment.TickCount);
Thread.Sleep(random.Next(1, 500));
var data = new byte[random.Next(1, 5)];
Console.WriteLine("A bus message of {0} bytes", data.Length);
return data;
};
static void Main(string[] args)
{
try
{
var program = new Program();
var cts = new CancellationTokenSource(Timeout.Infinite); // cancel in 10s
var task1 = program.ReadTelegramAsync(1, cts.Token);
var task2 = program.ReadTelegramAsync(2, cts.Token);
var task3 = program.ReadTelegramAsync(3, cts.Token);
var busTask = program.ReadBusAsync(cts.Token);
Task.WaitAll(task1, task2, task3);
Console.WriteLine("All telegrams received");
cts.Cancel(); // stop ReadBusAsync
}
catch (Exception ex)
{
while (ex is AggregateException)
ex = ex.InnerException;
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
}
}
Also, this scenario appears to be an ideal candidate for implementation using Reactive Extensions (Rx). As time allows, I'll show how to do that.
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;
}