Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to throw exception to caller from async method?

Today I read a lot about async/await and it completely blew my mind. I can't understand why the following test passed.

[Test]
public void Test()
{
    var listener = new AsyncHttpListener();
    listener.ListeningAsync();

    try
    {
        new WebClient().DownloadString("http://localhost:8080/");
    }
    catch (Exception)
    {
    }

    listener.Close();
}

public class AsyncHttpListener
{
    private readonly HttpListener listener;

    public AsyncHttpListener()
    {
        listener = new HttpListener();
        listener.Prefixes.Add("http://localhost:8080/");
        listener.Start();
    }

    public void Close()
    {
        listener.Close();
    }

    public async void ListeningAsync()
    {
        var context = await listener.GetContextAsync();
        HandleContext(context);
    }

    private void HandleContext(HttpListenerContext context)
    {
        throw new Exception("test excpetion");
    }
}


Test passed, but output contains:

System.Exception
test excpetion
   at AsyncHttpListenerTest.AsyncHttpListener.HandleContext(HttpListenerContext context) in AsyncHttpListener.cs: line 30
   at AsyncHttpListenerTest.AsyncHttpListener.d__0.MoveNext() in AsyncHttpListener.cs: line 25
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.b__1(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()

I expect that exception will be transmitted from task thread (HandleContext() method) to caller context and test fail. How can i get this behavior?

like image 504
Pavel Martynov Avatar asked Jan 17 '13 13:01

Pavel Martynov


People also ask

How do I get async exception?

To catch an exception that an async task throws, place the await expression in a try block, and catch the exception in a catch block. Uncomment the throw new Exception line in the example to demonstrate exception handling. The task's IsFaulted property is set to True , the task's Exception.

Does await throw exception?

If we use the “await” keyword, the exception will be thrown: This solves the problem for a single simple task, but what if we have multiple tasks running within a method?

Can async methods declare out parameters?

The only way to have supported out-by-reference parameters would be if the async feature were done by a low-level CLR rewrite instead of a compiler-rewrite.


1 Answers

Make your method async Task instead of async void, and make your test method async Task instead of void:

public async Task ListeningAsync()
{
    var context = await listener.GetContextAsync();
    HandleContext(context);
}

[Test]
public async Task Test()
{
    var listener = new AsyncHttpListener();
    await listener.ListeningAsync();

    try
    {
        new WebClient().DownloadString("http://localhost:8080/");
    }
    catch (Exception)
    {
    }

    listener.Close();
}

There are several good reasons to avoid async void. Error handling is one of them. Errors raised from async void methods go straight to the SynchronizationContext that was current when the method started.

The reason your test passed is because async methods may return to their caller before they complete. The test runner sees the test method return (without throwing an exception yet), and marks it as "passed". If you return Task from your test method, then the test runner knows to wait for the Task to complete before considering the test complete.

like image 107
Stephen Cleary Avatar answered Sep 27 '22 18:09

Stephen Cleary