How can I implement a timeout for a potentially long running action lambda? I have tried wrapping the action in a Task and then using a cancellation token to enforce the timeout but to no avail.
public void LongRunningTask()
{
ExecuteSafely(() => //do something );
}
private void ExecuteSafely(Action action)
{
try
{
action();
}
catch
{
// just don't crash
}
}
New implementation as per 1st answer:
public void Information<T>(string messageTemplate, T propertyValue)
{
ExecuteSafely(() => _logger.Information(messageTemplate, propertyValue));
}
private void ExecuteSafely(Action action)
{
try
{
var cts = new CancellationTokenSource();
cts.CancelAfter(new TimeSpan(0, 0, 1));
try
{
Task.Run(action, cts.Token).Wait();
}
catch (TaskCanceledException e)
{
System.Diagnostics.Debug.WriteLine(e.ToString());
//here you know the task is cancelled due to timeout
}
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.ToString());
// just don't crash
}
}
This is only working correctly when I step into Task.Run but not when I step over or let it run. Perhaps there is some closure issue with the lambda being passed into the action?
You should use CancellationTokenSource
void Execute(Action action, TimeSpan timeout)
{
var cts = new CancellationTokenSource();
cts.CancelAfter(timeout);
try
{
Task.Run(action, cts.Token).Wait();
}
catch (TaskCanceledException e)
{
//here you know the task is cancelled due to timeout
}
}
Related
I would like to replicate something that I have in Java to C#.
I'm NOT looking for, or anything that will involve the driver:
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
I'm using the http://www.awaitility.org/.
Here is the code:
public static void waitForElement(WebElement element) {
with()
.pollDelay(100, TimeUnit.MICROSECONDS)
.and()
.pollInterval(200, TimeUnit.MICROSECONDS)
.await()
.ignoreExceptions()
.until(() -> element.isDisplayed());
}
Thanks
I would do something like
public static async Task WaitForElementAsync(WebElement element)
{
await With(100, 200, true, () => element.isDisplayed());
}
private static async Task With(
int pollDeley,
int pollIntervall,
bool ignoreException,
Func<bool> until)
{
await Task.Delay(pollDeley);
var loop = true;
while (loop)
{
try
{
loop = !until();
if (!loop) break;
await Task.Delay(pollIntervall);
}
catch (Exception ex)
{
if (!ignoreException) throw;
}
}
}
but there might be a better solution if WebElement has an event like IsDisplayedChanged.
Also with this solution you introduce a async call line to your project (which in a web context can be beneficial), to avoid this you can replace the await Task.Delay(...) with Thread.Sleep(...).
Another solution would be to use a timer for the polling
private static async Task With(
int pollDeley,
int pollIntervall,
bool ignoreException,
Func<bool> until)
{
await Task.Delay(pollDeley);
var tcs = new TaskCompletionSource<bool>();
using (var timer = new Timer(pollIntervall))
{
void Poll(object sender, ElapsedEventArgs e)
{
try
{
if (until())
{
if (tcs.TrySetResult(true))
{
timer.Stop();
}
}
}
catch (Exception ex)
{
if (!ignoreException)
{
if (tcs.TrySetException(ex))
{
timer.Stop();
}
}
}
}
timer.Elapsed += Poll;
timer.Start();
await tcs.Task;
timer.Elapsed -= Poll;
}
}
I am trying to get a Windows Service running. The service should use a worker object to spawn multiple tasks.
I use SemaphoreSlim in both the worker object and each task to wait for events to finish, like so:
public static IHostBuilder ConfigureServices(this IHostBuilder builder)
{
builder.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<WorkerService>();
services.AddSingleton<WorkerClient>();
});
return builder;
}
WorkerService
public WorkerService(ILogger<WorkerService> logger, WorkerClient workerClient)
{
_logger = logger;
_workerClient = workerClient;
_bleClient.OnValuesReceived += _bleClient_OnValuesReceived;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
await _workerClient.Run();
}
catch(Exception ex)
{
_logger.LogCritical(ex, "Error while running worker client.");
}
await Task.Delay(TimeSpan.FromSeconds(_scanDelay), stoppingToken);
}
}
WorkerClient
public class WorkerClient
{
private Scanner _scanner;
private SemaphoreSlim _lock;
public WorkerClient()
{
_lock = new SemaphoreSlim(0, 1);
_scanner = new Scanner();
_scanner.OnScanFinished += scanner_ScanFinished;
}
public async Task Run()
{
_scanner.Scan();
await _lock.WaitAsync();
}
private void scanner_ScanFinished(object sender, string[] macs)
{
var tasks = new List<Task>();
foreach(var mac in macs)
{
var client = new TaskRunner(mac);
tasks.Add(client.Run());
}
if(tasks.Count > 0)
{
try
{
var task = Task.WhenAll(tasks.ToArray());
await task;
}
catch(Exception ex)
{
_logger.LogError(ex, ex.Message);
}
}
_lock.Release();
}
}
TaskRunner
public class TaskRunner
{
private SemaphoreSlim _lock;
private Client _client;
public TaskRunner(string mac)
{
_lock = new SemaphoreSlim(0, 1);
_client = new Client(mac);
_client.OnWorkFinished += client_WorkFinished;
}
public async Task Run()
{
_client.DoWork();
await _lock.WaitAsync();
}
private void client_WorkFinished(object sender, EventArgs args)
{
_lock.Release();
}
}
This whole construct runs fine when I launch it in a console or inside VS. But it hangs after 1-2 runs when I create a service using sc utility and start it.
I have no idea what I am doing wrong as I am very new to Windows Services and multithreading.
The SemaphoreSlim is probably not an adequate mechanism for converting an event to a Task, because it can't propagate exceptions. The TaskCompletionSource class is a more suitable mechanism for this purpose. Also when subscribing to an event it is a good idea to unsubscribe when we don't want to receive any further notifications. Unsubscribing is achieved using the -= operator.
Here are two extension methods for the classes Scanner and Client, that allow to subscribe to their specific events for a single notification, and propagate this notification as a Task.
public static class ScannerExtensions
{
public static Task<string[]> ScanAsync(this Scanner source)
{
var tcs = new TaskCompletionSource<string[]>();
Action<object, string[]> evenHandler = null;
evenHandler = (s, macs) =>
{
source.OnScanFinished -= evenHandler;
tcs.TrySetResult(macs);
};
source.OnScanFinished += evenHandler;
try
{
source.Scan();
}
catch (Exception ex)
{
source.OnScanFinished -= evenHandler;
tcs.SetException(ex);
}
return tcs.Task;
}
}
public static class ClientExtensions
{
public static Task DoWorkAsync(this Client source)
{
var tcs = new TaskCompletionSource<object>();
EventHandler evenHandler = null;
evenHandler = (s, e) =>
{
source.OnWorkFinished -= evenHandler;
tcs.TrySetResult(null);
};
source.OnWorkFinished += evenHandler;
try
{
source.DoWork();
}
catch (Exception ex)
{
source.OnWorkFinished -= evenHandler;
tcs.SetException(ex);
}
return tcs.Task;
}
}
You could use the extension methods Scanner.ScanAsync and Client.DoWorkAsync to refactor the ExecuteAsync method of your service like this:
private Scanner _scanner = new Scanner();
protected override async Task ExecuteAsync(CancellationToken token)
{
while (true)
{
Task delayTask = Task.Delay(TimeSpan.FromSeconds(_scanDelay), token);
try
{
string[] macs = await _scanner.ScanAsync();
Task[] doWorktasks = macs.Select(mac =>
{
var client = new Client(mac);
return client.DoWorkAsync();
}).ToArray();
await Task.WhenAll(doWorktasks);
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
}
await delayTask;
}
}
Not sure if this will solve your problem, but I think that it is a change in the right direction.
If the problem persists you could try creating and awaiting the client.DoWorkAsync tasks one at a time (instead of launching all of them concurrently), to see if that makes any difference.
I need to know how to stop task in pararell foreach loop when exception occurs? If Foo1() thrown an exception DoSomeWork() should be cancelled.
Item class
class Item
{
public async Task DoSomeWork()
{
try
{
await Foo1();
await Foo2();
}
catch (Exception ex)
{
Console.Writeline(ex.Message);
}
}
private async Task Foo1()
{
try
{
//
// some functionality
//
}
catch(Exception ex)
{
// If exception occurs, then how cancel Task DoSomeWork()
}
}
private async Task Foo2()
{
try
{
//
// some functionality
//
}
catch(Exception ex)
{
// If exception occurs, then how cancel Task DoSomeWork()
}
}
}
and the ItemCollection class with Pararell.Foreach loop
class ItemCollection
{
public void StartAsync()
{
try
{
IList<Task> il = new List<Task>();
Parallel.ForEach(ListOfItems, t => il.Add(t.DoSomeWork()));
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
How to use the CancellationToken property in this example?
Here is a good example from MSDN: https://msdn.microsoft.com/de-de/library/ee256691(v=vs.110).aspx
Before the loops starts:
CancellationTokenSource cts = new CancellationTokenSource();
ParallelOptions po = new ParallelOptions();
po.CancellationToken = cts.Token;
Inside the loop:
Parallel.ForEach(nums, po, (num) =>
{
// Do some work here
po.CancellationToken.ThrowIfCancellationRequested();
});
From another thread, you can trigger the cancel event:
cts.Cancel();
I am using an asynchronous method. How can I stop its execution when a Timer raises a timeout event?
My code:
public async Task<object> Method()
{
cts = new CancellationTokenSource();
try
{
timer = new System.Timers.Timer(3000);
timer.Start();
timer.Elapsed += (sender, e) =>
{
try
{
timer_Elapsed(sender, e, cts.Token, thread);
}
catch (OperationCanceledException)
{
return;
}
catch (Exception ex)
{
return;
}
};
await methodAsync(cts.Token);
return "message";
}
catch (OperationCanceledException)
{
return "cancelled";
}
catch (Exception ex)
{
return ex.Message;
}
}
// Async Call
public async Task<object> methodAsync(CancellationToken ct)
{
try
{
pdfDocument = htmlConverter.Convert("path", "");
}
catch(Exception ex)
{
return x.Message;
}
}
// Timer event
void timer_Elapsed(object sender, ElapsedEventArgs e, CancellationToken ct)
{
cts.Cancel();
ct.ThrowIfCancellationRequested();
}
Here's how canceling a task works:
public async Task<object> Method()
{
cts = new CancellationTokenSource();
await methodAsync(cts.Token);
return "message";
}
public Task<object> methodAsync(CancellationToken ct)
{
for (var i = 0; i < 1000000; i++)
{
if (ct.IsCancellationRequested)
{
break;
}
//Do a small part of the overall task based on `i`
}
return result;
}
You have to respond to the change in the property of ct.IsCancellationRequested to know when to cancel the task. There is no safe way for one thread/task to cancel another thread/task.
In your case it appears that you are trying to call a single method that doesn't know about the CancellationToken so you can not cancel this task safely. You must let the thread/task continue to completion.
I think you can try mentioning when to cancel it. Something like
cts.CancelAfter(TimeSpan.FromMilliseconds(5000));
Also, you need to use the cancellation token in the called methods. That's when you will know when to cancel.
I'm trying to cancel the execution of the DoSomethingAsync method which I call using await.
When I click on the cancel button the execution is not cancelled and I don't see the "Execution was cancelled" message box, but instead I see the other message box.
I don't understand why it's not working. I am still learning this part of C# and I took this example at http://www.codeproject.com/Articles/127291/C-vNext-New-Asynchronous-Pattern#heading0015 (I simplified it).
public class MyClass : Class
{
CancellationTokenSource cts;
private async void searchButton_Click(object sender, EventArgs e)
{
await DoSomethingAsync();
}
private void cancelButton_Click(object sender, EventArgs e)
{
cts.Cancel();
}
async void DoSomethingAsync()
{
cts = new CancellationTokenSource();
try
{
await SuperSlowProcess();
MessageBox.Show("You will only see this if execution is not cancelled");
}
catch (TaskCanceledException)
{
MessageBox.Show("Execution was cancelled");
}
}
}
In order to make it working, you actually need to use CancellationToken in the SuperSlowProcess:
public Task SuperSlowProcess(CancellationToken cancellationToken)
{
return Task.Run(() => {
// you need to check cancellationToken periodically to check if cancellation has been requested
for (int i = 0; i < 10; i++)
{
cancellationToken.ThrowIfCancellationRequested(); // this will throw OperationCancelledException after CancellationTokenSource.Cancel() is called
Thread.Sleep(200); // to emulate super slow process
}
});
}
Of course, it depends on the implementation of SuperSlowProcess. If it's not possible to check CancellationToken periodically, you could check it only once - in the very end, something like that:
public async Task SuperSlowProcess2(CancellationToken cancellationToken)
{
var response = await CallExternalApi();
cancellationToken.ThrowIfCancellationRequested();
}
and then
async void DoSomethingAsync()
{
cts = new CancellationTokenSource();
try
{
await SuperSlowProcess(cts.Token);
MessageBox.Show("You will only see this if execution is not cancelled");
}
catch (OperationCanceledException) // Note that exception type is different
{
MessageBox.Show("Execution was cancelled");
}
}