6

我正在构建一组 ASP.Net 托管的 WebAPI 服务,这些服务必须使用严重依赖于 HttpContext.Current 的旧库。我无法确保在参与异步调用的所有方法中都保留上下文。我在下面的代码中尝试了 await/Task.Wait 和 TaskScheduler.FromCurrentSynchronizationContext() 的几种变体。

    [HttpGet]
    public Task<IEnumerable<string>> ContinueWith()
    {
        Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");  //or another culture that is not the default on your machine
        Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;

        var output = new List<string> { TestOutput("Action start") };

        var task = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(1000);
                return TestOutput("In Task");
            }).ContinueWith(slowString =>
            {
                output.Add(slowString.Result);

                output.Add(TestOutput("Action end"));
                return output as IEnumerable<string>;
            });

        output.Add(TestOutput("Action Mid"));

        return task;
    }

    private string TestOutput(string label)
    {
        var s = label + " ThreadID: " + Thread.CurrentThread.ManagedThreadId.ToString(CultureInfo.InvariantCulture);
        s += " " + Thread.CurrentThread.CurrentCulture.EnglishName;
        s += HttpContext.Current == null ? " No Context" : " Has Context";
        Debug.WriteLine(s);
        return s;
    }

我希望能够确保 CurrentCulture 是 fr-FR,并且 HttpContext.Current 在调用 TestOutput 的每个点都不为空。对于我尝试过的任何东西,我都没有成功地为“任务中”调用做到这一点。同样在我的一些测试线程中,id 永远不会改变,这表明我已经有效地删除了该方法的异步性。如何确保在每次调用 TestOutput 时都保留区域性和 HttpContext.Current,并且代码可以在不同的线程上自由运行?

在闭包中捕获 HttpContext.Current 然后简单地再次设置它对我来说不起作用,因为我需要支持中等信任,这将在调用 HttpContext.Current 设置器时引发安全异常。

4

2 回答 2

8

有一点值得注意的事实是,HttpContext.Current是可写的。

var context = HttpContext.Current;
var task = Task.Factory.StartNew(() => {
    HttpContext.Current = context;
    // You may want to set CultureInformation here too.

    return TestOutput("In Task");
});
于 2012-11-26T07:10:34.277 回答
6

每当您执行任务时都会保留上下文await

您所看到的是没有线程池任务的上下文(Task.Run, ,或TaskFactory.StartNew就此而言BackgroundWorker或)。这是正常的,也是意料之中的。ThreadDelegate.BeginInvoke

所以,不要使用线程池任务。您的示例代码似乎想要使用具有 的多个线程进行并行处理HttpContext,这根本是不可能的。

您可以根据需要执行并发async方法,但这要求您Thread.Sleep实际上可以是一种async方法,而不是基于 CPU 的方法:

[HttpGet]
public async Task<IEnumerable<string>> Test()
{
  Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");
  Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;

  var output = new List<string> { TestOutput("Action start") };

  var task = SlowStringAsync();
  output.Add(TestOutput("Action Mid"));
  output.Add(await task);
  output.Add(TestOutput("Action end"));
  return output;
}

public async Task<string> SlowStringAsync()
{
  await Task.Delay(1000);
  return TestOutput("In Task");
}

如果您的旧库不在您的控制范围内并且您无法制作它async,那么您将不得不同步调用它。async在以下情况下从方法调用同步方法是可以接受的:

[HttpGet]
public async Task<IEnumerable<string>> Test()
{
  Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");
  Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;

  var output = new List<string> { TestOutput("Action start") };

  output.Add(TestOutput("Action Mid"));
  Thread.Sleep(1000);
  output.Add(TestOutput("Not Really In Task"));
  output.Add(TestOutput("Action end"));
  return output;
}
于 2012-11-26T02:55:44.343 回答