61

Quick question, I want to wait a second before launching an async task without a return value.
Is this the right way to do it?

Task.Delay(1000)
    .ContinueWith(t => _mq.Send(message))
    .Start();

What happens to exceptions?

tanguy_k
  • 11,307
  • 6
  • 54
  • 58
David
  • 3,736
  • 8
  • 33
  • 52
  • Why are you using the async CTP? The final release has been out for a long time. And if you can use an async method, you don't need to use ContinueWith manually - just await the delay. Could you give more context please? – Jon Skeet Oct 31 '13 at 10:21
  • 1
    yes it is the best way. for exception handling: http://stackoverflow.com/questions/12980712/what-is-the-best-way-to-catch-exception-in-task http://stackoverflow.com/questions/5983779/catch-exception-that-is-thrown-in-different-thread – Nahum Oct 31 '13 at 10:22
  • @jon: wrong tag :) it's being used inside a windows service. – David Oct 31 '13 at 10:24
  • @JonSkeet He may be restricted to .Net 4 (for XP compatibility) and not feel comfortable guffing around with compiler and targeting packs. This is similar to my position. – Gusdor Oct 31 '13 at 10:27
  • Even in .NET 4 you can use BCL Async. Acync CTP is only for .NET on VS 2010. BTW there is no need to call `Start()`, `Delay` has already started the task – Panagiotis Kanavos Oct 31 '13 at 14:07

4 Answers4

59

First of all, Start() only works on the (very rare) Tasks that were created using the Task constructor (e.g. new Task(() => _mq.Send(message))). In all other cases, it will throw an exception, because the Task is already started or waiting for another Task.

Now, probably the best way to do this would be to put the code into a separate async method and use await:

async Task SendWithDelay(Message message)
{
    await Task.Delay(1000);
    _mq.Send(message);
}

If you do this, any exception from the Send() method will end up in the returned Task.

If you don't want to do that, using ContinueWith() is a reasonable approach. In that case, exception would be in the Task returned from ContinueWith().

Also, depending on the type of _mq, consider using SendAsync(), if something like that is available.

svick
  • 236,525
  • 50
  • 385
  • 514
14

You can catch any exception thrown in the Task if you Wait for the Task to finish:

Be aware of that your Exception thrown in the Task is going to be the inner one

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Task task = Task.Delay(1000)
                .ContinueWith(t => Program.throwsException());

            task.Wait();     
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception:" + ex.Message); // Outputs: Exception:One or more errors occurred.
            Console.WriteLine("Inner exception:" + ex.InnerException.Message); // Outputs: Exception:thrown
        }
        Console.ReadKey();

    }
    static void throwsException()
    {
        Console.WriteLine("Method started");
        throw new Exception("thrown");
    }
}
Joakim Johansson
  • 3,196
  • 1
  • 27
  • 43
Attila
  • 3,206
  • 2
  • 31
  • 44
1

I wanted some code, which publishes two messages on the service bus with a delay between the messages, to run parallel. Here is my code:

var _ = _serviceBus.PublishAsync(message1)
        .ContinueWith(async f =>
        {
            if (f.IsFaulted)
            {
                logger.LogError(f.Exception, $"Exception was thrown while publishing message {f.Id}");                
            }
            await Task.Delay(5000);
            var _ = _serviceBus.PublishAsync(message2)
            .ContinueWith(f =>
            {
                logger.LogError(f.Exception, $"Exception was thrown while publishing message {f.Id}");
            }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
        }, TaskScheduler.Default);
Patrick Koorevaar
  • 1,219
  • 15
  • 24
  • 2
    Take a look at the guideline [CA2008: Do not create tasks without passing a TaskScheduler](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2008). The `ContinueWith` method is [low level](https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html), and needs to be handled with care. – Theodor Zoulias Aug 23 '22 at 12:01
  • Thanks for pointing this out! For me it's ok if the default Thread pool is used (I don't have a ui thread). Would it be better then to pass the TaskScheduler.Default as the TaskScheduler? `.ContinueWith(..., TaskScheduler.Default)` – Patrick Koorevaar Aug 23 '22 at 13:02
  • 2
    Yes, by specifying explicitly the `TaskScheduler.Default` you ensure that the continuation will be scheduled on a well known scheduler. The code is no longer sensitive to the ambient `TaskScheduler.Current`, whatever that may be. Making `TaskScheduler.Current` the default was an unfortunate design decision when the `ContinueWith` API was introduced, and now we are stuck with it. – Theodor Zoulias Aug 23 '22 at 13:34
0

You will be able to observe any exceptions if you Wait for the task.

Unhandled exceptions that are thrown by user code that is running inside a task are propagated back to the joining thread, except in certain scenarios that are described later in this topic. Exceptions are propagated when you use one of the static or instance Task.Wait or Task.Wait methods, and you handle them by enclosing the call in a try-catch statement.

Excerpt from Exception Handling (Task Parallel Library)

Be careful with timings. Tasks use a scheduler and are not guaranteed to start when your say 'go'. You code will guarantee at least 1000ms delay after telling it to Start but it is not guaranteed to be 1000ms exactly.

Gusdor
  • 14,001
  • 2
  • 52
  • 64