【问题标题】:How to work with HttpTaskAsyncHandler如何使用 HttpTaskAsyncHandler
【发布时间】:2012-08-09 03:58:29
【问题描述】:
public class FooHandler  : HttpTaskAsyncHandler
{
    public override async Task ProcessRequestAsync(HttpContext context)
    {
        return await new AdRequest().ProcessRequest();
        // getting error here. "Return type of async type is void"
    }
}

public class FooRequest
{

    public async Task<String> ProcessRequest()
    {
        //return await "foo"; obviously nothing to wait here
    }

}

我想做一个异步处理程序,只想返回一个字符串。我怎样才能让这个工作?是否有关于使用异步方法和任务的简明参考?

【问题讨论】:

    标签: c# .net asynchronous httptaskasynchandler


    【解决方案1】:

    几点:

    • 您可以await 任何Task,而不仅仅是从async 方法返回的那些。
    • async 方法将它们的返回值包装成 Task&lt;TResult&gt;;如果没有返回值,它们会将返回值本身包装到 Task 中。
    • 如果您不需要 async 方法的开销,可以使用多种便捷方法,例如 Task.FromResult
    • 如果你必须在其中使用await,则只创建一个方法async。如果您不需要制作方法async,请不要。

    您可能会发现我的async/await intro 很有帮助。

    public class FooHandler  : HttpTaskAsyncHandler
    {
      public override Task ProcessRequestAsync(HttpContext context)
      {
        return new AdRequest().ProcessRequest();
      }
    }
    
    public class AdRequest
    {
      public Task<String> ProcessRequest()
      {
        return Task.FromResult("foo");
      }
    }
    

    【讨论】:

    • Stephen 如果我改用 TCS:return new TaskCompletionSource&lt;object&gt;()... - 该方法只会在 setResult... 上退出...对吗?
    • 有点不对劲——但我又读了一遍你的文章,当你说——“不阻塞线程”时——你没有提到链条必须一直到顶部:例如:public void DoSomethingAsync() { doSomething1(); } public async Task doSomething1() { await Task.Delay(100); } - 这里一个线程将被阻塞(恕我直言),因为顶级方法不是异步的。(再次 - 恕我直言)。我想说的是应该添加这个。
    • 在您的示例中,没有线程被阻塞。我不确定我是否理解你的问题;你能发布一个实际的 SO 问题吗?
    • 这是一个不好的例子,因为实际上并没有使用任务,所以那些只是从 web 复制代码 sn-ps 的许多开发人员实际上会编写阻塞代码。还值得一提的是,特别是在网络环境中,异步方法中使用的对象不应在执行结束之前被释放。我将在答案中添加一个示例方法。
    • @user3285954:op特地问了如何同步实现一个异步接口方法(只返回一个字符串)。最简洁的方法 Task.FromResult.
    【解决方案2】:

    你不应该“返回”任务,编译器会隐式执行它,因为它是一个异步函数:

    public override async Task ProcessRequestAsync(HttpContext context)
    {
        await new AdRequest().ProcessRequest();
    }
    
    public async Task<String> ProcessRequest()
    {
        return "foo";
    }
    

    这是另一种方式,更接近您尝试做的事情:(没有 async/await)

    public override Task ProcessRequestAsync(HttpContext context)
    {
        return new AdRequest().ProcessRequest();
    }
    
    public Task<String> ProcessRequest()
    {
        return Task.Return("foo");
    }
    

    async 的一般引用是here 本质上将async 修饰符添加到方法中,使其隐式返回Task。如果你返回一个 int,它将把它变成一个Task&lt;int&gt;await 则相反,将Task&lt;int&gt; 变成int

    【讨论】:

      【解决方案3】:

      这是一个真正的异步方法:

      public Task<string> ProcessRequest()
      {
          var textFile = File.OpenText("file.txt");
          var readTask = textFile.ReadToEndAsync();
      
          readTask.ContinueWith(previousTask => textFile.Dispose());
      
          return readTask;
      }
      

      如果您使用大文件或慢速驱动器上的文件运行此方法,则执行将在文件读取结束之前返回调用者很久。在 Stephen Cleary 的示例中,调用者只有在计算完结果 ("foo") 后才能取回控制权。

      Dispose 必须在 ContinueWith 中,因为方法执行将在文件读取完成之前返回调用方,因此无法在 ProcessRequest 方法中关闭文件。

      当然可以开始自己的任务。

      public Task<string> ProcessRequest(CancellationToken cancellationToken)
      {
          var readTask = Task.Run(() =>
          {
              using (var textFile = File.OpenText("file.txt"))
              {
                  var text = textFile.ReadToEnd();
      
                  cancellationToken.ThrowIfCancellationRequested();
      
                  var processedText = text.Replace("foo", "bar");
      
                  return processedText;
              }
          });
      
          return readTask;
      }
      

      最好有一个 CancellationToken 并定期检查是否请求取消以允许取消长时间运行的操作。

      编辑 1

      正如@Stephen Cleary 突出显示的第一个样本,这导致大致或可能完全相同的 CIL:

      public async Task<string> ProcessRequest()
      {
          using (var textFile = File.OpenText("file.txt"))
          {
              var s = await textFile.ReadToEndAsync();
      
              return s;
          }
      }
      

      基本上,编译器会将 await textFile.ReadToEndAsync() 之后的代码转换为 ContinueWith。

      每种语法都有其优点,我的偏好是 1-2 行(即 dispose 和 log)进入 ContinueWith,更复杂的 continuation 使用 await。

      【讨论】:

      • 您的第一个示例并不比 using (var textFile = ...) { return await textFile.ReadToEndAsync(); } 好 - 除了 using+await 方法更清晰。第二个例子更危险;一般来说,你应该避免在 ASP.NET 上使用Task.Run
      • 正确,await 基本上只是用 ContinueWith 代替 Wait 的语法糖。背景可能更复杂,但区别的核心是这个。 async 是这个关键字的错误选择,因为许多开发人员认为它控制方法是否异步。我在示例和代码中也避免 await 的原因是,对于那些不太熟悉 TPL 的人来说,这使得代码更难理解,隐藏了正在发生的事情。我只在延续太复杂的情况下将其用作最后的手段,但对于本示例中的 1-2 行,ContinueWith 是完美的。
      • @Stephen Cleary 为什么要在 ASP.Net 中避免 Task.Run?并非所有 API 都是基于任务的,如果使用类似的 api,他们除了任务之外别无选择,或者他们必须阻止 ASP.Net 线程,这比启动新任务(实际上是线程)更糟糕。除了需要更复杂的代码来管理之外,启动线程将是相同的。您是否暗示在任务运行时请求可能会超时?
      • 完全不同意。 “异步”并不意味着“在另一个线程上运行”,await 创建的代码远比ContinueWith 更易于维护,Task.Run 应该特别避免,因为它确实使用了另一个线程,干扰了 ASP.NET 线程池。
      • 当使用任务时,除了线程之外还有什么可以使调用异步?该线程可能在另一个进程中,但最终仍然是一个线程。您能否详细说明 ASP.Net 和 Task.Run?我从未读过有关此的警告,一般建议似乎是,除非调用很短并且使用任务的 CPU 绑定更好。使用或不使用任务的答案,即在控制器方法中并不那么简单,但有很多关于这个主题的文章,即asp.net/mvc/overview/performance/…
      猜你喜欢
      • 2018-07-09
      • 1970-01-01
      • 1970-01-01
      • 2017-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-29
      • 1970-01-01
      相关资源
      最近更新 更多