1

I have an asynchronous method in which I wish to invoke the SendAsync method as a fire-and-forget service. Consider the following code:

public async Task HandleAsync(DoStuffCommand command)
{
    /*
    * Performing various tasks..
    */

    _client.SendAsync(myObject);
}

public async Task SendAsync(MyObject myObject)
{
    /*
    * Time consuming tasks..
    */  
    try
    {
        await call_1();
        Trace.TraceInformation("Call1");

        await call_2();
        Trace.TraceInformation("Call2");
    }
    catch (Exception e)
    {
        Trace.TraceError(e.Message);
        throw;
    }   
}

My problem is that for some reason call_2 is inconsistently getting called (very rarely). My suspicion is that the SendAsync method is not allowed to complete because the calling method HandleAsync does not await SendAsync and when the HandleAsync thread is terminated, the work in progress in SendAsync is too.

But, this is contrary to my understanding of async/await. I was under the impression that the SendAsync implementation would perform its work on a different thread in this scenario, and thus, be able to complete even if HandleAsync would return before SendAsync.

Maybe someone more async/await than myself can shed some light? Thank you.

UPDATE

I also tried adding a try/catch and traces. No exceptions are thrown but the trace consistently follows the behavior of the method calls, i.e. When both calls are made, so are both TraceInformation and when only call_1 is invoked, only the first TraceInformation runs.

Marcus
  • 8,230
  • 11
  • 61
  • 88
  • Looking at your code, you're invoking `_client.SendAsync` synchronously because there's no await to your call to it. Perhaps call_1 is throwing an exception hence preventing call_2 from executing ? – auburg May 23 '16 at 14:15
  • That was also a possible reason I investigated so I added traces and a try/catch around the logic within the `SendAsync` method but as far as I can tell, no exceptions are thrown. See update. – Marcus May 23 '16 at 14:18
  • @Marcus: What's the context - is this ASP.NET? – Stephen Cleary May 23 '16 at 14:24
  • This is in the domain layer (class library) of a Web API solution. – Marcus May 23 '16 at 14:26
  • 1
    "I was under the impression that the SendAsync implementation would perform its work on a different thread" - that's *not* wait `async`/`await` *does*. `async` does not conjure threads into existence. – Damien_The_Unbeliever May 23 '16 at 14:37
  • Ok, meaning that - the `SendAsync` is not allowed to complete because the current thread dies when `HandleAsync` completes? – Marcus May 23 '16 at 14:38
  • [By default, when an incomplete Task is awaited, the current “context” is captured and used to resume the method when the Task completes.](https://msdn.microsoft.com/en-us/magazine/jj991977.aspx) It appears that HandleAsync runs in a single-threaded context, which means that the SendAsync continuation will run in the same single-threaded context. If the thread terminates, then there is no more context in which to run the continuation. [This answer](http://stackoverflow.com/a/26236579/902497) discusses ways of overriding the default. – Raymond Chen May 23 '16 at 14:40
  • async methods share execution context with the caller. To make SendAsync() work in fire and forget mode _reliably_ you would need a "message broker" service such as RabitMQ, MSMQ, or Azure as Sephen suggested... – alexm May 23 '16 at 15:34

1 Answers1

3

I have an asynchronous method in which I wish to invoke the SendAsync method as a fire-and-forget service.

As I describe on my blog, fire-and-forget on ASP.NET is inherently dangerous and almost always the wrong solution to whatever requirement you're trying to meet. One of the main problems with "fire and forget" is that the background work is forgotten. There will always be some situations (hopefully rare) where the background work does not run, or is silently dropped. The only way to prevent this loss of work is to implement a reliable distributed system architecture.

My suspicion is that the SendAsync method is not allowed to complete because the calling method HandleAsync does not await SendAsync and when the HandleAsync thread is terminated, the work in progress in SendAsync is too.

ASP.NET is less about threads than it is about request contexts. When HandleAsync completes, the request context for that request is disposed, and that may cause some code in SendAsync to fail or behave erratically.

I was under the impression that the SendAsync implementation would perform its work on a different thread in this scenario, and thus, be able to complete even if HandleAsync would return before SendAsync.

No, absolutely not. async does not mean "run on another thread". If you want to run on another thread, then you have to do so explicitly (e.g., Task.Run). However, this is almost always a bad idea on ASP.NET.

If you want to minimize the loss of work, then use HostingEnvironment.QueueBackgroundWorkItem; if you want to prevent the loss of work, then either reject the fire-and-forget requirement or implement a proper distributed architecture. A proper distributed architecture is one with a reliable message queue (such as Azure Queues or MSMQ) with an independent worker process (such as Azure Functions or Win32 Services).

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thank you for clarifying. It seems to me however that a "request context" is a bit vague. When an ASP.NET application receives a request a RequestContext is instantiated by the runtime, but it seems to me a bit unclear what it really _is_ and what it _does_ in this context. Somewhere underneath the hood, I suspect we will get down to threading anyway? Even if I would explicitly spawn a new thread using `Task.Run` it would have the same result when the RequestContext is disposed, wouldn´t it? – Marcus May 24 '16 at 07:13
  • @Marcus: I'm actually not referring to the `RequestContext` class (which I did not even know existed until now). The "request context" is vague because it's never been documented (and it's a concept, not a type). It manages `HttpContext.Current`, culture, security, and possibly some other more obscure things. It is *not* a thread; it can move between threads over the course of an asynchronous request (though only one thread can be in it at a time). A `Task.Run` action would run *outside* a request context. – Stephen Cleary May 24 '16 at 12:24
  • I am aware that it is both a concept and a class, which obscures even further. Given that a `Task.Run` would run "_outside_ a request context" - does this mean that it will run til completion regardless of when the calling method returns? – Marcus May 24 '16 at 12:44
  • 1
    @Marcus: No; running code outside the request context is dangerous on ASP.NET because your app is occasionally recycled, which kills all non-request work. – Stephen Cleary May 24 '16 at 13:06