Good day.
I'm having a problem exiting a task with the cancellation token.
My program freezes when I get to the token2.ThrowIfCancellationRequested();.
Following it with the breakpoints is shows that the token2 is cancelled, but the program doesn't revert back to the previous sub routine where I try and catch
try
{
Task.Run(() => SendData_DoWork(_tokenSource3));
}
catch (OperationCanceledException ex)
{
SetText("Communivation error with device");
SetText("");
}
finally
{
token.Dispose();
}
}//comms routine
//send Meter Address to communicate to meter
private void SendData_DoWork(CancellationTokenSource token)
{
var token2 = token.Token;
var _tokenSource4 = new CancellationTokenSource();
try
{
timer.Interval = 10000;
timer.Start();
timer.Elapsed += OnTimerElapsed;
NetworkStream stream = client.GetStream();
SerialConverter serialConverter = new SerialConverter();
Thread.Sleep(1000);
string newtext = null;
newtext = $"/?{address}!\r\n";
SetText("TX: " + newtext);
byte[] newData = stringSend(newtext);
stream.Write(newData, 0, newData.Length);
Thread.Sleep(50);
byte[] message = new byte[23];
int byteRead;
while (true)
{
byteRead = 0;
try
{
byteRead = stream.Read(message, 0, 23);
if (message[0] == (char)0x15)
{
token.Cancel();
}
}
catch
{
token.Cancel();
}
if ((byteRead == 0))
{
token.Cancel();
}
timer.Stop();
timer.Dispose();
ASCIIEncoding encoder = new ASCIIEncoding();
string newresponse = encoder.GetString(serialConverter.convertFromSerial(message));
SetText("RX: " + newresponse);
if (newresponse[0].ToString() == SOH)
{
token.Cancel();
}
if (newresponse != null)
{
/* NEXT SUB ROUTINE*/
}
else { break; }
}//while looop
}//try
catch (Exception ex)
{
token.Cancel();
}
if (token2.IsCancellationRequested)
{
timer.Stop();
timer.Dispose();
token2.ThrowIfCancellationRequested();
}
}//sendData subroutine
You are launching a Task, and ignoring the result; the only time Task.Run would throw is if the task-method is invalid, or enqueuing the operation itself failed. If you want to know how SendData_DoWork ended, you'll need to actually check the result of the task, by capturing the result of Task.Run and awaiting it (preferably asynchronously, although if we're talking async, SendData_DoWork should probably also be async and return a Task).
Your catch/finally will probably be exited long before SendData_DoWork even starts - again: Task.Run just takes the time required to validate and enqueue the operation; not wait for it to happen.
I think you have missunderstood how cancellation tokens are supposed to work. Your work method should take a CancellationToken, not a CancellationTokenSource. And it should call ThrowIfCancellationRequested inside the loop, not after. I would suspect that you would get some issues with multiple cancel calls to the same cancellation token.
Typically you would use a pattern something like like this:
public void MyCancelButtonHandler(...) => cts.Cancel();
public async void MyButtonHandler(...){
try{
cts = new CancellationTokenSource(); // update shared field
await Task.Run(() => MyBackgroundWork(cts.Token));
}
catch(OperationCancelledException){} // Ignore
catch(Exception){} // handle other exceptions
}
private void MyBackgroundWork(CancellationToken cancel){
while(...){
cancel.ThrowIfCancellationRequested();
// Do actual work
}
}
So in my particular case it seems like changing the sub-routines from private async void ... to private async Task fixes the particular issue that I'm having.
Related
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;
}
I wrote an async method with retry logic. It works just fine, however recently I wanted to add a timeout for each try in case the operation takes too long.
public static async Task<Result> PerformAsync(Func<Task> Delegate,
Func<Exception, bool> FailureCallback = null, int Timeout = 30000,
int Delay = 1000, int Threshold = 10)
{
if (Delegate == null)
{
throw new ArgumentNullException(nameof(Delegate));
}
if (Threshold < 1)
{
throw new ArgumentOutOfRangeException(nameof(Threshold));
}
CancellationTokenSource Source = new CancellationTokenSource();
CancellationToken Token = Source.Token;
bool IsSuccess = false;
for (int Attempt = 0; Attempt <= Threshold && !Source.IsCancellationRequested;
Attempt++)
{
try
{
await Delegate();
Source.Cancel();
IsSuccess = true;
break;
}
catch (Exception E)
{
Exceptions.Add(E);
if (FailureCallback != null)
{
bool IsCanceled =
Application.Current.Dispatcher.Invoke(new Func<bool>(() =>
{
return !FailureCallback(E);
}));
if (IsCanceled)
{
Source.Cancel();
IsSuccess = false;
break;
}
}
}
await Task.Delay(Delay);
}
return new Result(IsSuccess, new AggregateException(Exceptions));
}
I've been trying various solutions all over the web, but for whatever reason I've never managed to set timeout for each try individually.
I tried to do this using Task.WhenAny() with Task.Delay(Timeout), but when I launch my program, FailureCallback is called only once and if another try fails, FailureCallback is not called.
Ok, lets start. First of all, the intended usage of a CancellationToken isn't to cancel locally a loop, that's a waste, a CancellationToken reserves some resources and in your case you can simply usea boolean.
bool IsSuccess = false;
bool IsCancelled = false;
for (int Attempt = 0; Attempt <= Threshold; Attempt++)
{
try
{
await Delegate();
IsSuccess = true;
//You are breaking the for loop, no need to test the boolean
//in the for conditions
break;
}
catch (Exception E)
{
Exceptions.Add(E);
if (FailureCallback != null)
{
IsCancelled = Application.Current.Dispatcher.Invoke(new Func<bool>(() =>
{
return !FailureCallback(E);
}));
//You are breaking the for loop, no need to test the boolean
//in the for conditions
if(IsCancelled)
break;
}
}
await Task.Delay(Delay);
}
//Here you have "IsSuccess" and "IsCancelled" to know what happened in the loop
//If IsCancelled is true the operation was cancelled, if IsSuccess is true
//the operation was success, if both are false the attempt surpased threshold.
Second, you must update your delegate to be cancellable, that's the real intended usage of CancellationToken, make your delegate to expect a CancellationToken and use it properly inside the function.
public static async Task<Result> PerformAsync(Func<CancellationToken, Task> Delegate, //..
//This is an example of the Delegate function
public Task MyDelegateImplemented(CancellationToken Token)
{
//If you have a loop check if it's cancelled in each iteration
while(true)
{
//Throw a TaskCanceledException if the cancellation has been requested
Token.ThrowIfCancellationRequested();
//Now you must propagate the token to any async function
//that accepts it
//Let's suppose you are downloading a web page
HttpClient client;
//...
await client.SendAsync(message, Token)
}
}
Finally, now that your task is cancellable you can implement the timeout like this:
//This is the "try" in your loop
try
{
var tokenSource = new CancellationTokenSource();
var call = Delegate(tokenSource.Token);
var delay = Task.Delay(timeout, tokenSource.Token);
var finishedTask = await Task.WaitAny(new Task[]{ call, delay });
//Here call has finished or delay has finished, one will
//still be running so you need to cancel it
tokenSource.Cancel();
tokenSource.Dispose();
//WaitAny will return the task index that has finished
//so if it's 0 is the call to your function, else it's the timeout
if(finishedTask == 0)
{
IsSuccess = true;
break;
}
else
{
//Task has timed out, handle the retry as you need.
}
}
I've an existing code I wrote some time ago, that works but I dislike the fact that the thread I start remains in loop.
This piece of code is a consumer on an IBMMQ code, waiting for messages to be processed.The problem I've is that with the following code
private Task ExecuteQueuePolling(CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() =>
{
ConnectToAccessQueue();
Logger.Debug($"Accessed to the queue {queueName}");
Logger.DebugFormat("Repeating timer started, checking frequency: {checkingFrequency}",
checkingFrequency);
while (!cancellationToken.IsCancellationRequested)
{
Logger.Trace( () => "Listening on queues for new messages");
// isChecking = true;
var mqMsg = new MQMessage();
var mqGetMsgOpts = new MQGetMessageOptions
{ WaitInterval = (int)checkingFrequency.TotalMilliseconds };
// 15 second limit for waiting
mqGetMsgOpts.Options |= MQC.MQGMO_WAIT | MQC.MQGMO_FAIL_IF_QUIESCING |
MQC.MQCNO_RECONNECT_Q_MGR | MQC.MQOO_INPUT_AS_Q_DEF;
try
{
mqQueue.Get(mqMsg, mqGetMsgOpts);
if (string.Compare(mqMsg.Format, MQC.MQFMT_STRING, StringComparison.Ordinal) == 0)
{
var text = mqMsg.ReadString(mqMsg.MessageLength);
Logger.Debug($"Message received : [{text}]");
Message message = new Message { Content = text };
foreach (var observer in observers)
observer.OnNext(message);
}
else
{
Logger.Warn("Non-text message");
}
}
catch (MQException ex)
{
if (ex.Message == MQC.MQRC_NO_MSG_AVAILABLE.ToString())
{
Logger.Trace("No messages available");
//nothing to do, emtpy queue
}
else if (ex.Message == MQC.MQRC_CONNECTION_BROKEN.ToString())
{
Logger.ErrorException("MQ Exception, trying to recconect", ex);
throw new ReconnectException();
}
}
Thread.Sleep(100);
}
},cancellationToken);
}
//Calling method
try
{
string queueManagerName = configuration.GetValue<string>("IBMMQ:QUEUE_MANAGER_NAME");
// var queueManager = new MQQueueManager(queueManagerName,dictionary2);
QueueMonitor monitor = new QueueMonitor(configuration, "IMPORTER_RECEIVER_TEST");
//_subscription = monitor.Subscribe(receiver);
await monitor.StartAsync(cts.Token).ConfigureAwait(false);
}
catch (Exception e)
{
log.Error(e, "Error creating the queue monitor or it's subscription");
}
finally
{
WaitForCancel(cts);
}
The call to await monitor.StartAsync(cts.Token).ConfigureAwait(false); remains pending.
How should I modify my code, so that the call returns and in background the task continue to loop?
Thanks in advance
Here is how you can simplify your code by replacing Thread.Sleep with Task.Delay:
private async Task ExecuteQueuePolling(CancellationToken cancellationToken)
{
while (true)
{
// Process mqQueue here
await Task.Delay(100, cancellationToken);
}
}
Task.Delay has the advantage that accepts a CancellationToken, so in case of cancellation the loop will exit immediately. This could be important if the pooling of the MQ was lazier (for example every 5 seconds).
private static Task _runningTask;
static void Main(string[] args)
{
var cts = new CancellationTokenSource();
_runningTask = ExecuteQueuePolling(cts.Token);
WaitForCancel(cts);
}
private static void WaitForCancel(CancellationTokenSource cts)
{
var spinner = new SpinWait();
while (!cts.IsCancellationRequested
&& _runningTask.Status == TaskStatus.Running) spinner.SpinOnce();
}
private static Task ExecuteQueuePolling(CancellationToken cancellationToken)
{
var t = new Task(() =>
{
while (!cancellationToken.IsCancellationRequested)
; // your code
if (cancellationToken.IsCancellationRequested)
throw new OperationCanceledException();
}, cancellationToken, TaskCreationOptions.LongRunning);
t.Start();
return t;
}
So I am talking to something externally and I have just sent it a message, I will expect an almost immediate response but I will wait for a second in case there is a delay. A separate thread is monitoring for an input and will set a flag "newdataflag" when it has received this data. All I am trying to do below is wait in a while loop until this flag is set or 1 second elapses.
private bool WaitrxData()
{
System.Windows.Threading.DispatcherTimer waitrxtimer = new System.Windows.Threading.DispatcherTimer();
waitrxtimer.Tick += waitrxtimer_Tick;
waitrxtimer.Interval = TimeSpan.FromMilliseconds(10);
waitrxtimer.IsEnabled = true;
waitrxtimer.Start();
statusText.Text = "Waiting For Response";
//wait for new data
while (!newdataflag)
{
if (waitrxcounter > 100)
{
statusText.Text = "No Response";
break;
}
}
waitrxtimer.Stop();
if (waitrxcounter > 100)
{
return false;
}
else
{
newdataflag = false;
return true;
}
}
private void waitrxtimer_Tick(object sender, EventArgs e)
{
waitrxcounter++;
}
This code works if the response is so immediate the while loop is skipped but if not, the code will not execute the timer and just get stuck in the while loop and crash. I think this is because the timer is not creating a new thread to tick like I thought it would?
Maybe I am doing the wrong thing?
Cheers
Instead of busy waiting in a loop you could await Task.Delay() with a CancellationToken:
private CancellationTokenSource cts = new CancellationTokenSource();
...
private async Task<bool> Wait()
{
try
{
await Task.Delay(1000, cts.Token);
return true;
}
catch
{
return false;
}
}
// cancel Wait() from another method
cts.Cancel();
I All,
I have to monitor an async task, which has to be cancellable and do not perform more than specific Time To Live.
I already knew about the following code.
CancellationTokenSource l_cts = new CancellationTokenSource(timemillis);
which will doing the cancellation ( as far i monitor the token in my async method).
However, this NOT gave me any information about WHY he has been cancelled, Timeout or user cancellation? furthermore, the timeout event is delayed while i did not catch the cancellation with
Token.ThrowIfCancellationRequested();
In order to solve these issues, I wrote the timeout process as follow.
static async Task TestAsync(int processDelaySeconds, int cancelDelaySeconds, int timeoutDelaySeconds )
{
CancellationTokenSource l_cts = new CancellationTokenSource();
// the process to monitor
Task l_process = new Task((state) =>
{
Console.WriteLine("Process BEGIN");
// dummy loop
for (int l_i = 0; l_i != processDelaySeconds; l_i++)
{
Thread.Sleep(1000);
l_cts.Token.ThrowIfCancellationRequested();
}
Console.WriteLine("Process END");
}, null, l_cts.Token);
// register timeout
RegisteredWaitHandle l_rwh = ThreadPool.RegisterWaitForSingleObject(l_cts.Token.WaitHandle,
(state, timedOut) =>
{
if (timedOut)
{
l_cts.Cancel();
Console.WriteLine("Timed out");
}
else
{
Console.WriteLine("Cancel Signaled");
}
},
null, (int)TimeSpan.FromSeconds(timeoutDelaySeconds).TotalMilliseconds, true);
// cancel task
if (cancelDelaySeconds > 0)
{
Task l_cancel = new Task(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(cancelDelaySeconds));
l_cts.Cancel();
});
l_cancel.Start();
}
try
{
l_process.Start();
await l_process;
}
catch (OperationCanceledException)
{
Console.WriteLine("Task Cancelled");
}
finally
{
// be sure to unregister the wait handle to cancel the timeout
if (l_process.Status != TaskStatus.Canceled) l_rwh.Unregister(l_cts.Token.WaitHandle);
}
Console.WriteLine("Task Status is : {0}", l_process.Status);
}
static async void Tests()
{
Console.WriteLine("NORMAL PROCESS");
Console.WriteLine("--------------");
await TestAsync(2, 10, 10);
Console.WriteLine();
Console.WriteLine("CANCEL");
Console.WriteLine("------");
await TestAsync(5, 2, 10);
Console.WriteLine();
Console.WriteLine("TIMEOUT");
Console.WriteLine("-------");
await TestAsync(10, 15, 2);
}
Then My question is :
Is there any drawbacks or traps behind the scene ?
A better and more efficient way ??
ps- Goal is performance, not shorter code.
If you need to distinguish between the user's and time-out cancellation, you can use CreateLinkedTokenSource:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp
{
internal class Program
{
// worker
private static void DoWork(CancellationToken token)
{
for (int i = 0; i < 1000; i++)
{
token.ThrowIfCancellationRequested();
Thread.Sleep(100); // do the work item
}
token.ThrowIfCancellationRequested();
}
// test
private static void Main()
{
var userCt = new CancellationTokenSource();
var combinedCt = CancellationTokenSource.CreateLinkedTokenSource(
userCt.Token);
combinedCt.CancelAfter(3000); // cancel in 3 seconds
Console.CancelKeyPress += (s, e) =>
{
e.Cancel = true;
userCt.Cancel();
};
var task = Task.Run(
() => DoWork(combinedCt.Token),
combinedCt.Token);
try
{
task.Wait();
}
catch (AggregateException ex)
{
Console.WriteLine(ex.InnerException.Message);
if (task.IsCanceled)
{
if (userCt.Token.IsCancellationRequested)
Console.WriteLine("Cancelled by user");
else if (combinedCt.Token.IsCancellationRequested)
Console.WriteLine("Cancelled by time-out");
else
Console.WriteLine("Cancelled by neither user nor time-out");
}
}
}
}
}
As to your original code, you really did not need ThreadPool.RegisterWaitForSingleObject(l_cts.Token.WaitHandle, ...), there's CancellationToken.Register for that, which returns an IDisposable ready for use with using.
In order to know if your Task has been cancelled or timed out, you can use the Task.WaitAny overload which takes a TimeSpan:
// Index will return -1 if timeout has occured, otherwise will print the index of the completed task
var cnclToken = new CancellationTokenSource().Token
var yourTask = Task.Run(() => { /* Do stuff */ }, cnclToken);
var index = Task.WhenAny(new[] { yourTask }, TimeSpan.FromSeconds(1));
http://msdn.microsoft.com/en-us/library/dd235645(v=vs.110).aspx