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);
}
Related
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!
If have 2 methods I want to run in parallel on different threads, let's call them Task1 and Task2. If Task2 completes before Task1 then I want the results of both combined into a list. If Task1 completes and Task2 is still running, it should cancel Task2 to avoid further processing. WhenAll doesn't work because it will wait for both tasks. WhenAny doesn't work because it will return if either finish first.
This is the solution I came up with.
static void Main(string[] args)
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var rates = new List<string>();
try
{
var p44Task = Task.Factory.StartNew(() => GetP44Rates(token), token);
var mgRates = await GetMGRates();
rates.AddRange(mgRates);
if (p44Task.IsCompleted && p44Task.Result.IsCompleted)
{
rates.AddRange(p44Task.Result.Result);
}
else
{
// Cancel the p44 task
tokenSource.Cancel();
}
}
catch (Exception e)
{
tokenSource.Cancel();
Console.WriteLine(e);
}
foreach (var rate in rates)
{
Console.WriteLine(rate);
}
Console.ReadLine();
}
private static async Task<List<string>> GetMGRates(CancellationToken cancellationToken)
{
var rates = new List<string>();
for (var i = 0; i < 10; i++)
{
rates.Add($"MG: {i}");
Console.WriteLine($"MG inside {i}");
await Task.Delay(1000);
}
return rates;
}
private static async Task<List<string>> GetP44Rates(CancellationToken ct)
{
var rates = new List<string>();
for (var i = 0; i < 50; i++)
{
rates.Add($"P44: {i}");
Console.WriteLine($"P44: {i}");
await Task.Delay(1000);
if (ct.IsCancellationRequested)
{
Console.WriteLine("bye from p44.");
Console.WriteLine("\nPress Enter to quit.");
ct.ThrowIfCancellationRequested();
}
}
return rates;
}
In my case, I created tasks by:
IList<Task> Tasks = new List<Task>();
Tasks.Add(Task.Run(async () => { await functionAsync();}));
I need these tasks to run infinitely in order to continuously processing some incoming data. However, in cases when some fatal error/exceptions happen, I need to end the program by canceling all the tasks. I put together an simple example to simulate what I intended to do , but it does not work. I thought a task will considered to be end when throw an exception, and the WaitAny should return with AggregatedException, but it doesn't seem to be how it actually works. So, how can I make it correct?
public static void Main(string[] args)
{
Console.WriteLine(nameof(Main));
for (int i = 1; i < 5; i++)
{
var i1 = i;
_tasks.Add(Task.Run(async () => { await Tryme(i1); }));
}
try
{
Task.WaitAny(_tasks.ToArray());
}
catch (Exception e)
{
Console.WriteLine("Stop my program if any of the task in _tasks throw exception");
Console.WriteLine(e);
}
Console.ReadLine();
}
private static async Task Tryme(int i)
{
Console.WriteLine($"I'm {i}");
if (i == 3)
{
Console.WriteLine($"{i} is throwing the exception");
throw new Exception("fake one");
}
await Task.Delay(TimeSpan.MaxValue);
}
Instead of manually canceling the entire task-chain, you can use TPL Dataflow which falts the entire block for you if an unhandeled exception occurs, or can be configured to behave in other modes if needed:
var actionBlock = new ActionBlock<int>(i => TryMe(i));
foreach (var num in Enumerable.Range(0, 100))
{
actionBlock.Post(num);
}
try
{
await actionBlock.Completion();
}
catch (Exception e)
{
// Block finished prematurely, handle exception.
}
Note Dataflow takes care of parralisation for you, no need to manually create tasks.
Got some clue from this post . It looks that WaitAny will not throw any exception. I got the exception by:
int faultIndex = Task.WaitAny(_tasks.ToArray());
if (_tasks[faultIndex].IsFaulted)
{
Console.WriteLine($"{faultIndex} end");
throw _tasks[faultIndex].Exception;
}
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);
}
}
}
As you can see in this code:
public async void TaskDelayTest()
{
while (LoopCheck)
{
for (int i = 0; i < 100; i++)
{
textBox1.Text = i.ToString();
await Task.Delay(1000);
}
}
}
I want it to set textbox to string value of i with one second period until I set LoopCheck value to false . But what it does is that it creates all iteration ones in for all and even if I set LoopCheck value to false it still does what it does asyncronously.
I want to cancel all awaited Task.Delay() iteration when I set LoopCheck=false. How can I cancel it?
Use the overload of Task.Delay which accepts a CancellationToken
public async Task TaskDelayTest(CancellationToken token)
{
while (LoopCheck)
{
token.throwIfCancellationRequested();
for (int i = 0; i < 100; i++)
{
textBox1.Text = i.ToString();
await Task.Delay(1000, token);
}
}
}
var tokenSource = new CancellationTokenSource();
TaskDelayTest(tokenSource.Token);
...
tokenSource.Cancel();
If you're going to poll, poll on a CancellationToken:
public async Task TaskDelayTestAsync(CancellationToken token)
{
for (int i = 0; i < 100; i++)
{
textBox1.Text = i.ToString();
await Task.Delay(TimeSpan.FromSeconds(1), token);
}
}
For more information, see the cancellation documentation.
Just a slight comment about having a cancellation token, and using a try-catch to stop it throwing an exception - your iteration block might fail due to a different reason, or it might fail due to a different task getting cancelled (e.g. from an http request timing out in a sub method), so to have the cancellation token not throw an exception you might want a bit more complicated catch block
public async void TaskDelayTest(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
for (int i = 0; i < 100; i++)
{
try
{
textBox1.Text = i.ToString();
await DoSomethingThatMightFail();
await Task.Delay(1000, token);
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
//task is cancelled, return or do something else
return;
}
catch(Exception ex)
{
//this is an actual error, log/throw/dostuff here
}
}
}
}
After running into this problem I wrote a drop in replacement that behaves as expected if you want to do polling:
public static class TaskDelaySafe
{
public static async Task Delay(int millisecondsDelay, CancellationToken cancellationToken)
{
await Task.Delay(TimeSpan.FromMilliseconds(millisecondsDelay), cancellationToken);
}
public static async Task Delay(TimeSpan delay, CancellationToken cancellationToken)
{
var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var task = new TaskCompletionSource<int>();
tokenSource.Token.Register(() => task.SetResult(0));
await Task.WhenAny(
Task.Delay(delay, CancellationToken.None),
task.Task);
}
}
It uses a cancellation token callback to complete a task and then awaits either that synthetic task or the normal Task.Delay with no cancellation token. This way it won't throw an exception when the source token is cancelled, but still responds to the cancellation by returning execution. You still need to check the IsCancellationRequested after calling it to decide what to do if it is cancelled.
Unit tests, if anyone is interested:
[Test]
public async Task TaskDelay_WaitAlongTime()
{
var sw = System.Diagnostics.Stopwatch.StartNew();
await Base.Framework.TaskDelaySafe.Delay(System.TimeSpan.FromSeconds(5), System.Threading.CancellationToken.None);
Assert.IsTrue(sw.Elapsed > System.TimeSpan.FromSeconds(4));
}
[Test]
public async Task TaskDelay_DoesNotWaitAlongTime()
{
var tokenSource = new System.Threading.CancellationTokenSource(250);
var sw = System.Diagnostics.Stopwatch.StartNew();
await Base.Framework.TaskDelaySafe.Delay(System.TimeSpan.FromSeconds(5), tokenSource.Token);
Assert.IsTrue(sw.Elapsed < System.TimeSpan.FromSeconds(1));
}
[Test]
public async Task TaskDelay_PrecancelledToken()
{
var tokenSource = new System.Threading.CancellationTokenSource();
tokenSource.Cancel();
var sw = System.Diagnostics.Stopwatch.StartNew();
await Base.Framework.TaskDelaySafe.Delay(System.TimeSpan.FromSeconds(5), tokenSource.Token);
Assert.IsTrue(sw.Elapsed < System.TimeSpan.FromSeconds(1));
}
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static DateTime start;
static CancellationTokenSource tokenSource;
static void Main(string[] args)
{
start = DateTime.Now;
Console.WriteLine(start);
TaskDelayTest();
TaskCancel();
Console.ReadKey();
}
public static async void TaskCancel()
{
await Task.Delay(3000);
tokenSource?.Cancel();
DateTime end = DateTime.Now;
Console.WriteLine(end);
Console.WriteLine((end - start).TotalMilliseconds);
}
public static async void TaskDelayTest()
{
tokenSource = new CancellationTokenSource();
try
{
await Task.Delay(2000, tokenSource.Token);
DateTime end = DateTime.Now;
Console.WriteLine(end);
Console.WriteLine((end - start).TotalMilliseconds);
}
catch (TaskCanceledException ex)
{
Console.WriteLine(ex.Message);
}
finally
{
tokenSource.Dispose();
tokenSource = null;
}
}
}
}