繁体   English   中英

使用Task.Run而不是Delegate.BeginInvoke

[英]Use Task.Run instead of Delegate.BeginInvoke

我最近将我的项目升级到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);
}

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

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);
    }
}

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

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

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

就像是:

ThreadPool.QueueUserWorkItem(
            x =>
                {
                    try
                    {
                        // Do something
                        ...
                    }
                    catch (Exception e)
                    {
                        // Log something
                        ...
                    }
                });

我不得不为不同的异步调用方法做一些性能基准测试,我发现(毫不奇怪) 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; } } 

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

我将使用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);

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

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

审计肯定不是我称之为“火与忘记”的东西。 请记住,在ASP.NET上, “fire and forget”意味着“我不关心这段代码是否实际执行” 因此,如果您希望的语义可能偶尔会丢失审核,那么(并且只有这样)您可以使用fire并忘记审核。

如果要确保审核完全正确,则在发送响应之前等待审核保存完成,或者将审核信息排队到可靠存储(例如,Azure队列或MSMQ)并具有独立后端(例如,Azure worker角色或Win32服务)处理该队列中的审核。

但是如果你想要危险地生活(接受偶尔可能缺少审计),你可以通过向ASP.NET运行时注册工作来缓解这些问题。 使用我博客中BackgroundTaskManager

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

  BackgroundTaskManager.Run(() =>
  {
    try
    {
      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();
    }
    catch (Exception e)
    {
      var username = WebSecurity.CurrentUserName;
      Debugging.DispatchExceptionEmail(e, username);
    }
  });

  base.OnActionExecuting(filterContext);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM