Asynchronous Fire and Forget With Lambdas

code 0 comments suggest edit

I’ve been having trouble getting to sleep lately, so I thought last night that I would put that to use and hack on Subtext a bit. While doing so, I ran into an old Asynchronous Fire and Forget helper method written way back by Mike Woodring which allows you to easily call a delegate asynchronously.

On the face of it, it seems like you could simply call BeginInvoke on the delegate and be done with it, Mike’s code addresses a concern with that approach:

Starting with the 1.1 release of the .NET Framework, the SDK docs now carry a caution that mandates calling EndInvoke on delegates you’ve called BeginInvoke on in order to avoid potential leaks. This means you cannot simply “fire-and-forget” a call to BeginInvoke without the risk of running the risk of causing problems.

The mandate he’s referring to, I believe, is this clause in the MSDN docs:

No matter which technique you use, always call EndInvoke to complete your asynchronous call.

Note that it doesn’t explicitly say “or you will get a memory leak”. But a little digging turns up the following comment in the MSDN forums.

The reason that you should call EndInvoke is because the results of the invocation (even if there is no return value) must be cached by .NET until EndInvoke is called.  For example if the invoked code throws an exception then the exception is cached in the invocation data.  Until you call EndInvoke it remains in memory.  After you call EndInvoke the memory can be released.  For this particular case it is possible the memory will remain until the process shuts down because the data is maintained internally by the invocation code.  I guess the GC might eventually collect it but I don’t know how the GC would know that you have abandoned the data vs. just taking a really long time to retrieve it.  I doubt it does.  Hence a memory leak can occur.

The thread continues to have some back and forth and doesn’t appear to be conclusive either way, but this post by Don Box gives a very pragmatic argument.

…the reality is that some implementations rely on the EndXXX call to clean up resources.  Sometimes you can get away with it, but in the general case you can’t.

In other words, why take the chance? In any case, much of this discussion is made redundant with the C# 3.0 Action class combined with ThreadPool.QueueUserWorkItem aka (QUWI)

Here is the code in Subtext for sending email, more or less. I have to define a delegate and then pass that to FireAndForget

// declaration
delegate bool SendEmailDelegate(string to, 
      string from, 
      string subject, 
      string body);

//... in a method body
SendEmailDelegate sendEmail = im.Send;
AsyncHelper.FireAndForget(sendEmail, to, from, subject, body);

This code relies on the FireAndForget method which I show here. Note I am not showing the full code. I just wanted to point out that the arguments to the delegate are not strongly typed. They are simply an array of objects which provide no guidance to how many arguments you need to pass.

public static void FireAndForget(Delegate d, params object[] args)
{
  ThreadPool.QueueUserWorkItem(dynamicInvokeShim, 
    new TargetInfo(d, args));
}

Also notice that this implementation uses QUWI under the hood.

With C# 3.0, there is no need to abstract away the call to QUWI. Just pass in a lambda, which provides the benefit that you’re calling the actual method directly so you get Intellisense for the argumennts etc… So all that code gets replaced with:

ThreadPool.QueueUserWorkItem(callback => im.Send(to, from, subject, body));

Much cleaner and I get to get rid of more code! As I’ve said before, the only thing better than writing code is getting rid of code!

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

19 responses

  1. Avatar for Johannessen
    Johannessen January 9th, 2009

    I you are into post sharp, then you will get away with this clean solution :)

    public class FireAndForgetAttribute : OnMethodInvocationAspect
    {
    public override void OnInvocation(MethodInvocationEventArgs eventArgs)
    {
    ThreadPool.QueueUserWorkItem(delegate { eventArgs.Proceed(); });
    }
    }

    and use it like:

    ...
    [FireAndForget]
    public bool SendEmailDelegate(string to, string from,
    string subject, string body)
    {
    ...
    }
    ...

    :)

  2. Avatar for Alex
    Alex January 9th, 2009

    Is there any reason this wouldn't work in a similar manner, even in .Net 2.0?
    string to, from, subject, body;
    SendEmailDelegate sendEmail = im.Send;
    ThreadPool.QueueUserWorkItem(delegate {
    sendEmail(to, from, subject, body);
    });

  3. Avatar for haacked
    haacked January 9th, 2009

    @Johannessen Nice AOP approach to the problem! :)
    @Alex True! I think the code I was refactoring was written in the 1.1 days and I completely skipped a 2.0 version to jump right to how I would do it today. :)

  4. Avatar for Stuart
    Stuart January 9th, 2009

    Great tip, I love lambdas too!
    Just a small typo at the end I think
    Thread.QueueUserWorkItem(callback => im.Send(to, from, subject, body));
    Should be
    ThreadPool.QueueUserWorkItem(callback => im.Send(to, from, subject, body));

  5. Avatar for Morgan Cheng
    Morgan Cheng January 9th, 2009

    IMHO, If BeginInvoke throws a exception, then no need to call EndInvoke.

  6. Avatar for Justin Chase
    Justin Chase January 9th, 2009

    I hate to be a downer but I'm going to have to say this is a bad idea. Honestly, when do you ever want to fire and forget? Your example there is about sending an email message, you really aught to be baking in some mechanism for handling exceptions otherwise this is an equivalent crime to catch(Exception) { }
    How about:

    delegate void AsyncResult<TResult>(TResult result, Exception error);
    delegate void AsyncResult(Exception error);
    delegate bool SendEmailDelegate(string to,
    string from,
    string subject,
    string body);
    public static void Fire(this Delegate d, AsyncResult result, params object[] args)
    {
    ThreadPool.QueueUserWorkItem(() =>
    {
    Exception error = null;
    try
    {
    dynamicInvokeShim();
    }
    catch(Exception ex)
    {
    error = ex;
    }
    finally
    {
    result(ex);
    }
    },
    new TargetInfo(d, args));
    }

    Then have another overload for the AsyncResult<TResult> delegate for methods with expected return results. Call it like this:

    // adding "this" to the method makes it an extension method
    im.Send.Fire(e => { if(e!=null) Log.Error(e); },
    to, from, subject, body);

    If you had a nice static log method that was smart enough to check the null for you you could pass that instead of a lambda also.

  7. Avatar for JD Conley
    JD Conley January 10th, 2009

    <async_propganda>
    As a scalability/performance junkie (and a subtext user) I'd like to say that if you're sending any quantity of email all of these are bad ideas. :) Using QueueUserWorkItem to create fake asynchronicity in an asp.net application is just asking for the "Server too Busy" error. What you gain in short term user pleasure you lose in crunch time scalability as you now use up 2x the limited, precious, worker threads. And besides, threads should be processing, not waiting for IO!
    Waiting around for an email to finish sending (or any IO) in an extra worker threadpool thread takes away from the amount of threads you have available for rendering asp.net pages and handling ado.net calls. In the scalability sense, if you have to do IO synchronously you might as well just do it in the original asp.net thread since QueueUserWorkItem uses the same threadpool as your asp.net page processing.
    The best option here is to use the SendAsync method provided on the System.Net.Mail.SmtpClient class and provide a lambda on the SendCompleted event. This does real asynchronous processing using async IO and IOCP behind the scenes. If you want the page to finish processing rather than go into async rendering mode you can temporarily remove the asp.net async context with System.Threading.SynchronizationContext.SetSynchronizationContext(null). I have this little helper class that does it:
    using System;
    using System.Threading;
    namespace Hive7.Core
    {
    public class SynchronizationContextSwitcher : IDisposable
    {
    private SynchronizationContext _oldContext;
    public SynchronizationContextSwitcher() : this(null)
    {
    }
    public SynchronizationContextSwitcher(SynchronizationContext context)
    {
    _oldContext = SynchronizationContext.Current;
    SynchronizationContext.SetSynchronizationContext(context);
    }
    #region IDisposable Members
    public void Dispose()
    {
    SynchronizationContext.SetSynchronizationContext(_oldContext);
    _oldContext = null;
    }
    #endregion
    }
    }
    If you can't use the SmtpClient class for some reason and are stuck with the synchronous mail sending option, you should spawn a single long running thread in application_start that polls a thread safe queue, and post to that queue for work, or use some threadpool other than the built-in worker one.
    </async_propganda>

  8. Avatar for Florian Kr&#252;sch
    Florian Kr&#252;sch January 10th, 2009

    Keep in mind that apart from throwing an exception, QueueUserWorkItem could just return "false".

  9. Avatar for haacked
    haacked January 11th, 2009

    @JDConley one thing we do is use a custom threadpool which doesn't take a thread from the ASP.NET ThreadPool (which is the same threadpool used when using the async programming model AFAIK).

  10. Avatar for PK
    PK January 11th, 2009

    @Haacked: can u please elaborate on this using code snippets: "use a custom threadpool which doesn't take a thread from the ASP.NET ThreadPool". please?

  11. Avatar for JD Conley
    JD Conley January 11th, 2009

    @haacked: The async programming model exposed by the framework does things very differently than simply blocking a (worker pool or otherwise) thread behind the scenes. In fact the entire point is to not use a thread unless it's doing CPU intensive things rather than waiting on IO operations.
    In any case. What threadpool are you using? I just saw QueueUserWorkItem.

  12. Avatar for Richard
    Richard January 11th, 2009

    @JDConley: The Disposed method of your SynchronizationContextSwitcher class should be checking to make sure that it's running on the same thread as the constructor, and that the current context hasn't been changed since the constructor was called.
    private ExecutionContext _executionContext;
    private readonly SynchronizationContext _oldContext;
    private readonly SynchronizationContext _newContext;
    public SynchronizationContextSwitcher(SynchronizationContext context)
    {
    _newContext = context;
    _executionContext = Thread.CurrentThread.ExecutionContext;
    _oldContext = SynchronizationContext.Current;
    SynchronizationContext.SetSynchronizationContext(context);
    }
    public void Dispose()
    {
    if (null != _executionContext)
    {
    if (_executionContext != Thread.CurrentThread.ExecutionContext)
    {
    throw new InvalidOperationException("Dispose called on wrong thread.");
    }
    if (_newContext != SynchronizationContext.Current)
    {
    throw new InvalidOperationException("The SynchronizationContext has changed.");
    }

    SynchronizationContext.SetSynchronizationContext(_oldContext);
    _executionContext = null;
    }
    }

  13. Avatar for Brian Chiasson
    Brian Chiasson January 14th, 2009

    @Haacked - I believe JDConley is right, perhaps you can get to the bottom of this and get back to us. According to the ThreadPool documentation (link below) there is only one ThreadPool available per process. So, if you are in a web farm or web garden you probably won't see too much of a problem - multiple ASP.NET worker processes. But if you have a single IIS site, then you are consuming ASP.NET threads to send out e-mail. Someone correct me if I am wrong please.

  14. Avatar for Brian Chiasson
    Brian Chiasson January 14th, 2009

    I knew I was going to forget the URL.
    msdn.microsoft.com/.../...hreading.threadpool.aspx

  15. Avatar for Jarrett
    Jarrett January 14th, 2009

    Taking a look at the Oxite source code, they create background services which all fire periodically using a System.Threading.Timer. So, as long as each background service doesn't fire off any additional threads, that will be only two threads for the SendMessages background service. However, the downfall is that each timer eats up a thread from the pool. If there are 10 background services, thats the potential for 20+ fewer threads in the pool at all times. So this sounds like a good solution as long as there are only a couple background services?

  16. Avatar for haacked
    haacked January 14th, 2009

    @Brian Yes, there's only one System.Thread.Threading.ThreadPool per process. But you can write you're own thread pool.
    Subtext uses this one: www.koders.com/.../...6D2DA45BA3D502389B154CB.aspx

  17. Avatar for Brian Chiasson
    Brian Chiasson January 14th, 2009

    @Haacked - I must have missed this when I was looking through the subtext code.

  18. Avatar for JD Conley
    JD Conley January 14th, 2009

    @Richard - Thanks for the improvement I just wrote about this nifty little class. It's been hanging out in one of our libraries for too long and needed exposed to the air! ;)

  19. Avatar for Alimentacao
    Alimentacao March 9th, 2012

    Good Work ! In fact the entire point is to not use a thread unless it's doing CPU intensive things rather than waiting on IO operations.
    Thanks for sharing!