使用Task.Run而不是Delegate.BeginInvoke

dim*_*uel 7 c# asynchronous

我最近将我的项目升级到ASP.NET 4.5,我等了很长时间才使用4.5的异步功能.阅读文档后,我不确定是否可以改进我的代码.

我想异步执行一个任务然后忘记它.我目前正在这样做的方式是创建委托然后使用BeginInvoke.

这是我项目中的一个过滤器,每次用户访问必须审计的资源时,都会在我们的数据库中创建一个审计:

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);
}
Run Code Online (Sandbox Code Playgroud)

但是为了完成这个异步任务,我需要始终定义一个回调,如下所示:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我宁愿不使用回调,因为我不需要异步调用任务的结果.

如何使用Task.Run()(或asyncawait)改进此代码?

Jos*_*sep 11

它可能听起来有点超出范围,但如果您只是想在启动后忘记,为什么不直接使用ThreadPool

就像是:

ThreadPool.QueueUserWorkItem(
            x =>
                {
                    try
                    {
                        // Do something
                        ...
                    }
                    catch (Exception e)
                    {
                        // Log something
                        ...
                    }
                });
Run Code Online (Sandbox Code Playgroud)

我不得不为不同的异步调用方法做一些性能基准测试,我发现(并不奇怪)ThreadPool工作得更好,但实际上,BeginInvoke并没有那么糟糕(我在.NET 4.5上).这就是我在帖子末尾发现的代码.我没有在网上找到这样的东西,所以我花时间自己检查一下.每个调用都不完全相同,但它在功能方面或多或少具有相同功能:

  1. ThreadPool:70.80ms
  2. Task:90.88ms
  3. BeginInvoke:121.88ms
  4. Thread:4657.52ms

    public class Program
    {
        public delegate void ThisDoesSomething();
    
        // Perform a very simple operation to see the overhead of
        // different async calls types.
        public static void Main(string[] args)
        {
            const int repetitions = 25;
            const int calls = 1000;
            var results = new List<Tuple<string, double>>();
    
            Console.WriteLine(
                "{0} parallel calls, {1} repetitions for better statistics\n", 
                calls, 
                repetitions);
    
            // Threads
            Console.Write("Running Threads");
            results.Add(new Tuple<string, double>("Threads", RunOnThreads(repetitions, calls)));
            Console.WriteLine();
    
            // BeginInvoke
            Console.Write("Running BeginInvoke");
            results.Add(new Tuple<string, double>("BeginInvoke", RunOnBeginInvoke(repetitions, calls)));
            Console.WriteLine();
    
            // Tasks
            Console.Write("Running Tasks");
            results.Add(new Tuple<string, double>("Tasks", RunOnTasks(repetitions, calls)));
            Console.WriteLine();
    
            // Thread Pool
            Console.Write("Running Thread pool");
            results.Add(new Tuple<string, double>("ThreadPool", RunOnThreadPool(repetitions, calls)));
            Console.WriteLine();
            Console.WriteLine();
    
            // Show results
            results = results.OrderBy(rs => rs.Item2).ToList();
            foreach (var result in results)
            {
                Console.WriteLine(
                    "{0}: Done in {1}ms avg", 
                    result.Item1,
                    (result.Item2 / repetitions).ToString("0.00"));
            }
    
            Console.WriteLine("Press a key to exit");
            Console.ReadKey();
        }
    
        /// <summary>
        /// The do stuff.
        /// </summary>
        public static void DoStuff()
        {
            Console.Write("*");
        }
    
        public static double RunOnThreads(int repetitions, int calls)
        {
            var totalMs = 0.0;
            for (var j = 0; j < repetitions; j++)
            {
                Console.Write(".");
                var toProcess = calls;
                var stopwatch = new Stopwatch();
                var resetEvent = new ManualResetEvent(false);
                var threadList = new List<Thread>();
                for (var i = 0; i < calls; i++)
                {
                    threadList.Add(new Thread(() =>
                    {
                        // Do something
                        DoStuff();
    
                        // Safely decrement the counter
                        if (Interlocked.Decrement(ref toProcess) == 0)
                        {
                            resetEvent.Set();
                        }
                    }));
                }
    
                stopwatch.Start();
                foreach (var thread in threadList)
                {
                    thread.Start();
                }
    
                resetEvent.WaitOne();
                stopwatch.Stop();
                totalMs += stopwatch.ElapsedMilliseconds;
            }
    
            return totalMs;
        }
    
        public static double RunOnThreadPool(int repetitions, int calls)
        {
            var totalMs = 0.0;
            for (var j = 0; j < repetitions; j++)
            {
                Console.Write(".");
                var toProcess = calls;
                var resetEvent = new ManualResetEvent(false);
                var stopwatch = new Stopwatch();
                var list = new List<int>();
                for (var i = 0; i < calls; i++)
                {
                    list.Add(i);
                }
    
                stopwatch.Start();
                for (var i = 0; i < calls; i++)
                {
                    ThreadPool.QueueUserWorkItem(
                        x =>
                        {
                            // Do something
                            DoStuff();
    
                            // Safely decrement the counter
                            if (Interlocked.Decrement(ref toProcess) == 0)
                            {
                                resetEvent.Set();
                            }
                        },
                        list[i]);
                }
    
                resetEvent.WaitOne();
                stopwatch.Stop();
                totalMs += stopwatch.ElapsedMilliseconds;
            }
    
            return totalMs;
        }
    
        public static double RunOnBeginInvoke(int repetitions, int calls)
        {
            var totalMs = 0.0;
            for (var j = 0; j < repetitions; j++)
            {
                Console.Write(".");
                var beginInvokeStopwatch = new Stopwatch();
                var delegateList = new List<ThisDoesSomething>();
                var resultsList = new List<IAsyncResult>();
                for (var i = 0; i < calls; i++)
                {
                    delegateList.Add(DoStuff);
                }
    
                beginInvokeStopwatch.Start();
                foreach (var delegateToCall in delegateList)
                {
                    resultsList.Add(delegateToCall.BeginInvoke(null, null));
                }
    
                // We lose a bit of accuracy, but if the loop is big enough,
                // it should not really matter
                while (resultsList.Any(rs => !rs.IsCompleted))
                {
                    Thread.Sleep(10);
                }
    
                beginInvokeStopwatch.Stop();
                totalMs += beginInvokeStopwatch.ElapsedMilliseconds;
            }
    
            return totalMs;
        }
    
        public static double RunOnTasks(int repetitions, int calls)
        {
            var totalMs = 0.0;
            for (var j = 0; j < repetitions; j++)
            {
                Console.Write(".");
                var resultsList = new List<Task>();
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (var i = 0; i < calls; i++)
                {
                    resultsList.Add(Task.Factory.StartNew(DoStuff));
                }
    
                // We lose a bit of accuracy, but if the loop is big enough,
                // it should not really matter
                while (resultsList.Any(task => !task.IsCompleted))
                {
                    Thread.Sleep(10);
                }
    
                stopwatch.Stop();
                totalMs += stopwatch.ElapsedMilliseconds;
            }
    
            return totalMs;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)


dca*_*tro 10

如果我正确地理解了您的要求,您想要启动任务然后忘记它.任务完成后,如果发生异常,您需要记录它.

Task.Run用来创建一个任务,然后ContinueWith附加一个延续任务.此延续任务将记录从父任务引发的任何异常.此外,使用TaskContinuationOptions.OnlyOnFaulted以确保持续只运行,如果发生异常.

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);
Run Code Online (Sandbox Code Playgroud)

作为附注,非常不鼓励使用ASP.NET中的后台任务和即发即弃场景.请参阅ASP.NET中实现重复后台任务的危险