使用WebForms Async / Await时访问响应对象

时间:2014-08-18 22:02:05

标签: c# asp.net webforms async-await

我有一个简单的.net 4.5 webforms项目,我用它来输出一些服务检查的结果。我已经创建了一个多线程和等待的服务检查类(每次检查可能需要1-3秒,我希望它们并行检查)。我希望每个服务检查的结果都写入网页并在确定后立即刷新(我不关心结果是否有序)。服务检查程序方法已经过测试,在控制台应用程序中运行良好,但我无法将其移植到webforms应用程序。

下面的代码有点工作(非常随机)。有时,它是完美的。其他时候,它会“跳过”显示其中一个测试的结果。其他时候,它将CSS样式的显示与结果混合在一起!但是,大多数情况下,它在CheckService中的Response.Flush行崩溃,说明“重叠的I / O操作正在进行中”,然后崩溃。

如何在每次检查完成后编写网页响应缓冲区并以稳定的方式显示结果?

这是aspx页面的代码......

<%@ Page Language="C#" Async="true" AutoEventWireup="true" Inherits="ServiceMonitor._Default" Codebehind="Default.aspx.cs" %>

这是背后的代码......

public partial class _Default : System.Web.UI.Page
{
  protected void Page_Load(object sender, EventArgs e)
  {
      Response.Write("<style type=\"text/css\">body {font-family: Arial;font-size: 10pt;}</style>");
      Response.Write("Beginning processing...<br/>");
      Response.Flush();
      RegisterAsyncTask(new PageAsyncTask(CheckServices));
  }

  protected async Task CheckServices()
  {
      Response.Write(string.Format("Starting checks at {0}<br/>", DateTime.Now.ToLongTimeString()));
      var tasks = new List<Task<ServiceStatus>>
      {
          Task.Run(() => CheckService("ServiceOne")),
          Task.Run(() => CheckService("ServiceTwo")),
          Task.Run(() => CheckService("ServiceThree"))
      };
      var checkResults = (await Task.WhenAll(tasks)).ToList();
      Response.Write(string.Format("Checking complete at {0}<br/>", DateTime.Now.ToLongTimeString()));
      Response.Flush();
  }

  public ServiceStatus CheckService(string serviceName)
  {
      var startTime = DateTime.Now;

      // Simulate a longer running process, by pausing for 1-3 seconds
      var random = new Random();
      var end = DateTime.Now + TimeSpan.FromMilliseconds(random.Next(1000, 3000)); 
      while (DateTime.Now < end) { }

      var elapsedTime = (DateTime.Now - startTime).TotalSeconds;
      var service = new ServiceStatus {Name = serviceName, IsRunning = true};
      Response.Write(string.Format("Done with {0} in {1} seconds<br/>", service.Name, elapsedTime.ToString("N2")));
      Response.Flush();
      return service;
    }
}

public class ServiceStatus
{
    public string Name { get; set; }
    public bool IsRunning { get; set; }
}

仅供参考 - 应用程序不应该很漂亮,因此删除了标准的HTML标记。它也不会被许多用户访问(实际上只是我),因此IIS阻塞并不是真正的问题,页面应该在20-30秒内返回。

1 个答案:

答案 0 :(得分:5)

您的问题是您尝试从随机后台线程(即Reponse.Write)访问请求上下文(即Task.Run)。

理想的解决方案是始终保持异步。这样,我的意思是让你的服务检查程序异步,但多线程。如果他们使用API​​ ping实施,那么您可以查看HttpClient以了解实施情况。

一旦他们异步,CheckService方法也会异步:

public async Task<ServiceStatus> CheckServiceAsync(string serviceName)
{
  var startTime = DateTime.Now;

  // Simulate a longer running process, by pausing for 1-3 seconds
  var random = new Random();
  var waitTime = TimeSpan.FromMilliseconds(random.Next(1000, 3000));
  await Task.Delay(waitTime);

  var elapsedTime = (DateTime.Now - startTime).TotalSeconds;
  var service = new ServiceStatus {Name = serviceName, IsRunning = true};
  Response.Write(string.Format("Done with {0} in {1} seconds<br/>", service.Name, elapsedTime.ToString("N2")));
  Response.Flush();
  return service;
}

它可以被称为并发,没有多线程

protected async Task CheckServicesAsync()
{
  Response.Write(string.Format("Starting checks at {0}<br/>", DateTime.Now.ToLongTimeString()));
  var tasks = new List<Task<ServiceStatus>>
  {
    CheckService("ServiceOne"),
    CheckService("ServiceTwo"),
    CheckService("ServiceThree")
  };
  var checkResults = (await Task.WhenAll(tasks)).ToList();
  Response.Write(string.Format("Checking complete at {0}<br/>", DateTime.Now.ToLongTimeString()));
  Response.Flush();
}