This question already has answers here:
Retry a task multiple times based on user input in case of an exception in task
(3 answers)
Closed 8 years ago.
I have method that is sending email using smtp server. Using Task.Factory I'm calling that method to not block UI:
Task.Factory.StartNew(() => SendMail("mail#example.com", "Test title", "TEST body"), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)
.ContinueWith(p =>
{
if (p.IsFaulted)
{
if (p.Exception != null)
{
MessageBox.Show(p.Exception.ToString());
}
return;
}
MessageBox.Show("ok");
}, TaskScheduler.FromCurrentSynchronizationContext());
Now I would like to modify my code to be able to try to call SendMail 10 times if something goes wrong. I've tried using do/while block, but I can't get this working:
private void button1_Click(object sender, EventArgs e)
{
bool success = false;
int i = 0;
int max = 10;
do
{
Task.Factory.StartNew(() => SendMail("mail#example.com", "Test", "TEST1"), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)
.ContinueWith(p =>
{
if (p.IsFaulted)
{
if (p.Exception != null)
{
MessageBox.Show(p.Exception.ToString());
}
return;
}
success = true;
MessageBox.Show("ok");
}, TaskScheduler.FromCurrentSynchronizationContext());
i++;
} while (!success && i < max);
if (!success)
{
MessageBox.Show("error");
}
else
{
MessageBox.Show("ok", "success", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
private void SendMail(string address, string title, string body)
{
Thread.Sleep(10000);
MailClient.Instance.Send(address, title, body);
}
That I would like to do is to be able to call Specific method inside task, if I get exception then I would like to call it again and again, 10 times, if after those 10 times it won't be successful I want to show exception.
This is a bit off topic, but every time I see someone using threading for IO bound operations i get the chills :)
As sending mail is a network bound operation, you can use the awaitable SmtpClient.SendMailAsync added in .NET 4.5.
If I may take the implementation posted by JoelC and refactor it a bit:
private int _maxAttempts = 10;
private async Task TrySendMailAsync(int attemptNumber)
{
var smtpClient = new SmtpClient();
var mailMsg = new MailMessage("from#test.com", "to#test.com", "Test Subject", "Test Body");
while (!success && attempts <= maxAttempts)
{
try
{
await smtpClient.SendMailAsync(mailMsg)).ConfigureAwait(false);
success = true;
}
catch
{
if (attempts >= maxAttempts)
{
throw;
}
}
attempts++;
}
}
This will let you loop as requested, but let the main job which is the async IO work without the unnessacery execution of a thread pool thread.
Something like this might fix the issue:
private int _maxAttempts = 10;
private void TrySendMail(int attemptNumber)
Task.Factory.StartNew(() => SendMail("mail#example.com", "Test title", "TEST body"), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)
.ContinueWith(p =>
{
attemptNumber++;
if (p.IsFaulted)
{
if (p.Exception != null)
{
if (_attempts < _maxAttempts)
{
// Try again
TrySendMail(attemptNumber);
}
else
{
MessageBox.Show(p.Exception.ToString());
}
}
return;
}
success = true;
MessageBox.Show("ok");
}, TaskScheduler.FromCurrentSynchronizationContext());
}
Its not the prettiest and you want to watch that you don't call it recursively too many times and get a stack overflow! Ten times should be fine.
EDIT:
I changed the attempts count to an argument to be safer with threading, in case you are calling this send mail possibly on a thread many times.
EDIT2:
The implementation of the method #mazharenko mentioned above could look something like this:
private void TryAndRepeat(Action routine, int maxAttempts)
{
int attempts = 1 ;
bool success = false;
while (!success && attempts <= maxAttempts)
{
try
{
routine.Invoke();
success = true;
}
catch
{
if (attempts >= maxAttempts)
{
throw;
}
}
attempts++;
}
}
Related
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.
I implemented Task synchronization using Monitor in C#.
However, I have read Monitor should not be used in asynchronous operation.
In the below code, how do I implement Monitor methods Wait and PulseAll with a construct that works with Task (asynchronous operations).
I have read that SemaphoreSlim.WaitAsync and Release methods can help.
But how do they fit in the below sample where multiple tasks need to wait on a lock object, and releasing the lock wakes up all waiting tasks ?
private bool m_condition = false;
private readonly Object m_lock = new Object();
private async Task<bool> SyncInteralWithPoolingAsync(
SyncDatabase db,
List<EntryUpdateInfo> updateList)
{
List<Task> activeTasks = new List<Task>();
int addedTasks = 0;
int removedTasks = 0;
foreach (EntryUpdateInfo entryUpdateInfo in updateList)
{
Monitor.Enter(m_lock);
//If 5 tasks are waiting in ProcessEntryAsync method
if(m_count >= 5)
{
//Do some batch processing to obtian values to set for adapterEntry.AdapterEntryId in ProcessEntryAsync
//.......
//.......
m_condition = true;
Monitor.PulseAll(m_lock); // Wakes all waiters AFTER lock is released
}
Monitor.Exit(m_lock);
removedTasks += activeTasks.RemoveAll(t => t.IsCompleted);
Task processingTask = Task.Run(
async () =>
{
await this.ProcessEntryAsync(
entryUpdateInfo,
db)
.ContinueWith(this.ProcessEntryCompleteAsync)
.ConfigureAwait(false);
});
activeTasks.Add(processingTask);
addedTasks++;
}
}
private async Task<bool> ProcessEntryAsync(SyncDatabase db, EntryUpdateInfo entryUpdateInfo)
{
SyncEntryAdapterData adapterEntry =
updateInfo.Entry.AdapterEntries.FirstOrDefault(e => e.AdapterId == this.Config.Id);
if (adapterEntry == null)
{
adapterEntry = new SyncEntryAdapterData()
{
SyncEntry = updateInfo.Entry,
AdapterId = this.Config.Id
};
updateInfo.Entry.AdapterEntries.Add(adapterEntry);
}
m_condition = false;
Monitor.Enter(m_lock);
while (!m_condition)
{
m_count++;
Monitor.Wait(m_lock);
}
m_count--;
adapterEntry.AdapterEntryId = .... //Set Value obtained form batch processing
Monitor.Exit(m_lock);
}
private void ProcessEntryCompleteAsync(Task<bool> task, object context)
{
EntryProcessingContext ctx = (EntryProcessingContext)context;
try
{
string message;
if (task.IsCanceled)
{
Logger.Warning("Processing was cancelled");
message = "The change was cancelled during processing";
}
else if (task.Exception != null)
{
Exception ex = task.Exception;
Logger.Warning("Processing failed with {0}: {1}", ex.GetType().FullName, ex.Message);
message = "An error occurred while synchronzing the changed.";
}
else
{
message = "The change was successfully synchronized";
if (task.Result)
{
//Processing
//...
//...
}
}
}
catch (Exception e)
{
Logger.Info(
"Caught an exception while completing entry processing. " + e);
}
finally
{
}
}
Thanks
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.
}
}
In my application I have a scenario where I need to block the UI Thread when I perform an action. While its blocked I have an other window that needs to show a message saying "performing action please wait" when the UI is blocked. Problem is the window shows up but it never displays the message. Please help. Even DoEvents() dosen't help. It just so lightly displays the message
Note:
I don't want the method as async because I want to block the UI when it runs.
I don't want to say ShowDialog() because it just blocks there.
private void ViewModel_PerformPrimeAction(InstrumentAction Action)
{
bool abort = false;
CommandRunningWindow cmdDialog = null;
if (Action == InstrumentAction.Prime)
{
if (Xceed.Wpf.Toolkit.MessageBox.Show((string)TryFindResource("ConfirmPrimeInstrument"),
ApplicationSettingsViewModel.Instance.ProductName, MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
return;
this.IsEnabled = false;
//This below line never shows the message.
cmdDialog = ShowCommandWindow(ViewModelsHelper.GetResourceString("PerformingPrime"));
System.Windows.Forms.Application.DoEvents();
}
UIUtils.OverrideCursor = System.Windows.Input.Cursors.Wait;
try
{
// This operation takes 10 seconds
QXInstrumentViewModel.Instance.Prime(() => { if (abort) throw new RunAbortedException(null); });
}
catch (RunAbortedException)
{
errorMessage = (string)TryFindResource("CompletePrimeInstrumentAborted");
cmdDialog?.Close();
}
catch (Exception ex)
{
var message = QXInstrumentViewModel.ToErrorCode(ex);
TokenSource = new System.Threading.CancellationToken(true);
if (message != null)
{
errorMessage = string.Format((string)TryFindResource("CompletePrimeInstrumentWithError"), Convert.ToInt32(message), errorMessage);
}
else
{
errorMessage = (string)TryFindResource("CompletePrimeInstrumentWithUnknownError");
}
cmdDialog?.Close();
}
UIUtils.OverrideCursor = null;
this.IsEnabled = true;
}
public CommandRunningWindow ShowCommandWindow(string message)
{
CommandRunningWindow cmdDialog = new CommandRunningWindow();
cmdDialog.Message = message;
cmdDialog.Owner = WPFUtils.GetActiveWindow();
cmdDialog.Show();
return cmdDialog;
}
The CommandRunningWindow has a dependency property of type string (message) that is bound to a textblock.
You can't both block and show a message on the same thread simultaneously.
What you should do is to execute your long-running operation on a background thread - the easiest way to do this is to start a TPL task - and display the message on the UI thread. You may still disable the window. Just make sure that you don't touch the UI on the background thread where your long-running operation executes.
private void ViewModel_PerformPrimeAction(InstrumentAction Action)
{
bool abort = false;
CommandRunningWindow cmdDialog = null;
if (Action == InstrumentAction.Prime)
{
if (Xceed.Wpf.Toolkit.MessageBox.Show((string)TryFindResource("ConfirmPrimeInstrument"),
ApplicationSettingsViewModel.Instance.ProductName, MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
return;
this.IsEnabled = false;
//This below line never shows the message.
cmdDialog = ShowCommandWindow(ViewModelsHelper.GetResourceString("PerformingPrime"));
}
UIUtils.OverrideCursor = System.Windows.Input.Cursors.Wait;
Task.Factory.StartNew(() =>
{
// This operation takes 10 seconds
QXInstrumentViewModel.Instance.Prime(() => { if (abort) throw new RunAbortedException(null); });
})
.ContinueWith(task =>
{
if (task.IsFaulted)
{
if (task.Exception != null && task.Exception.GetBaseException() is RunAbortedException)
{
var message = QXInstrumentViewModel.ToErrorCode(ex);
TokenSource = new System.Threading.CancellationToken(true);
if (message != null)
{
errorMessage = string.Format((string)TryFindResource("CompletePrimeInstrumentWithError"), Convert.ToInt32(message), errorMessage);
}
else
{
errorMessage = (string)TryFindResource("CompletePrimeInstrumentWithUnknownError");
}
}
else
{
errorMessage = (string)TryFindResource("CompletePrimeInstrumentAborted");
}
}
cmdDialog?.Close();
UIUtils.OverrideCursor = null;
this.IsEnabled = true;
}, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
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