Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Task.Run instead of Delegate.BeginInvoke

I have recently upgraded my projects to ASP.NET 4.5 and I have been waiting a long time to use 4.5's asynchronous capabilities. After reading the documentation I'm not sure whether I can improve my code at all.

I want to execute a task asynchronously and then forget about it. The way that I'm currently doing this is by creating delegates and then using BeginInvoke.

Here's one of the filters in my project with creates an audit in our database every time a user accesses a resource that must be audited:

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    var request = filterContext.HttpContext.Request;
    var id = WebSecurity.CurrentUserId;

    var invoker = new MethodInvoker(delegate
    {
        var audit = new Audit
        {
            Id = Guid.NewGuid(),
            IPAddress = request.UserHostAddress,
            UserId = id,
            Resource = request.RawUrl,
            Timestamp = DateTime.UtcNow
        };

        var database = (new NinjectBinder()).Kernel.Get<IDatabaseWorker>();
        database.Audits.InsertOrUpdate(audit);
        database.Save();
    });

    invoker.BeginInvoke(StopAsynchronousMethod, invoker);

    base.OnActionExecuting(filterContext);
}

But in order to finish this asynchronous task, I need to always define a callback, which looks like this:

public void StopAsynchronousMethod(IAsyncResult result)
{
    var state = (MethodInvoker)result.AsyncState;
    try
    {
        state.EndInvoke(result);
    }
    catch (Exception e)
    {
        var username = WebSecurity.CurrentUserName;
        Debugging.DispatchExceptionEmail(e, username);
    }
}

I would rather not use the callback at all due to the fact that I do not need a result from the task that I am invoking asynchronously.

How can I improve this code with Task.Run() (or async and await)?

like image 602
dimiguel Avatar asked Mar 27 '14 11:03

dimiguel


People also ask

How to run a method asynchronously in C#?

The simplest way to execute a method asynchronously is to start executing the method by calling the delegate's BeginInvoke method, do some work on the main thread, and then call the delegate's EndInvoke method. EndInvoke might block the calling thread because it does not return until the asynchronous call completes.

What is the difference between Invoke and BeginInvoke method in C#?

Invoke : Executes on the UI thread, but calling thread waits for completion before continuing. Control. BeginInvoke : Executes on the UI thread, and calling thread doesn't wait for completion.

How do I run a task in C#?

To start a task in C#, follow any of the below given ways. Use a delegate to start a task. Task t = new Task(delegate { PrintMessage(); }); t. Start();

How do you call a delegate in C#?

Delegates can be invoke like a normal function or Invoke() method. Multiple methods can be assigned to the delegate using "+" or "+=" operator and removed using "-" or "-=" operator. It is called multicast delegate. If a multicast delegate returns a value then it returns the value from the last assigned target method.


1 Answers

If I understood your requirements correctly, you want to kick off a task and then forget about it. When the task completes, and if an exception occurred, you want to log it.

I'd use Task.Run to create a task, followed by ContinueWith to attach a continuation task. This continuation task will log any exception that was thrown from the parent task. Also, use TaskContinuationOptions.OnlyOnFaulted to make sure the continuation only runs if an exception occurred.

Task.Run(() => {
    var audit = new Audit
        {
            Id = Guid.NewGuid(),
            IPAddress = request.UserHostAddress,
            UserId = id,
            Resource = request.RawUrl,
            Timestamp = DateTime.UtcNow
        };

    var database = (new NinjectBinder()).Kernel.Get<IDatabaseWorker>();
    database.Audits.InsertOrUpdate(audit);
    database.Save();

}).ContinueWith(task => {
    task.Exception.Handle(ex => {
        var username = WebSecurity.CurrentUserName;
        Debugging.DispatchExceptionEmail(ex, username);
    });

}, TaskContinuationOptions.OnlyOnFaulted);

As a side-note, background tasks and fire-and-forget scenarios in ASP.NET are highly discouraged. See The Dangers of Implementing Recurring Background Tasks In ASP.NET

like image 150
dcastro Avatar answered Sep 28 '22 01:09

dcastro