任务Outliving Http请求的生命周期

时间:2015-03-30 11:57:36

标签: c# .net multithreading task

我正在尝试在我的应用程序中实现一些异步代码。我开始一个新的任务,不要等待结果。此任务已经开始新闻另一个任务等待结果。第二个任务使用Http.Context(因为我需要从http上下文获取用户)作为辅助任务,我等待触发使用http.context.current.user的API调用。

我正在使用此answer以将当前上下文传递给任务。

所以我的代码如下:

            var context = HttpContext.Current;
            Task.Factory.StartNew(() =>
            {
                HttpContext.Current = context;
                ExecuteMethodAndContinue();
            });

    private static void ExecuteMethodAndContinue()
    {
        var myService = ServiceManager.GetMyService();
        var query = GetQuery();
        var files = myService.GetFiles(query).ToList();

        //Remaining code removed for brevity
    }

从代码中的其他位置调用的GetFiles的实现如下:

    public IDictionary<FileName, FileDetails> GetFiles(MyQuery query)
    {
        var countries = GetAllCountries();
        var context = HttpContext.Current;

        var taskList = countries.Select(c => Task.Factory.StartNew(() =>
        {
           HttpContext.Current = context;
           return new Dictionary<FileName, FileDetails> { { c, GetFilesInCountry(query, c) } };

        })).ToList();

        try
        {
            // Wait on all queries completing
            Task.WaitAll(taskList.ToArray<Task>());
        }
        catch (AggregateException ae)
        {
            throw new ApplicationException("Failed.", ae);
        }

        // Return collated results
        return taskList.SelectMany(t => t.Result).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
    }

实际包含依赖于Http.Context.Current.User的API调用的GetFilesInCountry方法。但是,当我在GetFiles中返回新行时遇到断点时,我可以看到http.current.context.user按预期正确设置。当我将鼠标悬停在GetFiles中的Http.Context.Current.User上时,我断开了GetFilesInCountry方法,我发现它是null。

我认为这是因为我开始第一次调用(ExecuteMethodAndContinue)的http请求已经完成,所以这就是当前上下文中的User为空的原因。

我能做些什么来正确解决这个问题吗?

1 个答案:

答案 0 :(得分:1)

最简单的方法当然是永远不要使用HttpContext.Current。无论如何,这不是一个好习惯 - 您只应访问与其关联的请求主题中的HttpContext。相反,您可以确保所有需要的方法,例如,用户名,获取用户名作为参数:

var username = HttpContext.Current.User.Identity.Name;

var taskList = countries.Select(c => Task.Factory.StartNew(() =>
{
  return new Dictionary<FileName, FileDetails> { { c, GetFilesInCountry(query, c, username) } };

})).ToList();

如果由于某种原因这是不切实际的(它可能不是很好的理由,但修复遗留应用程序就像这样可能是一件苦差事),你可以用一些东西替换HttpContext.Current访问更具体,而不是与特定请求相关联。而且,呃,线程安全:

public static class UserContext
{
  [ThreadStatic]
  public static string Username;
}

所以你的调用代码看起来像这样:

var username = HttpContext.Current.User.Identity.Name;

var taskList = countries.Select(c => Task.Factory.StartNew(() =>
{
  UserContext.Username = username;

  return new Dictionary<FileName, FileDetails> { { c, GetFilesInCountry(query, c) } };

})).ToList();

每当您通常使用HttpContext.Current.User.Identity.Name时,您都会使用UserContext.Username代替(请勿忘记在主请求中填写UserContext线程)。

巨大的警告,当你在那里有异步代码时它会变得非常疯狂;你在线程池上,所以你不是这些线程的独占用户,任何可用的线程池线程都可以自由地执行任何await或者延续(&# 39;没有编组到同步上下文)。因此,只要您通过手动Task.RunawaitContinueWith或其他任何方式创建更多任务,您就会失去这种情境。同样重要的是,没有地方可以清除这些信息 - 这显然是一个巨大的安全漏洞,因为并发请求可能会使代码的不同部分执行不同的用户上下文。如果你选择走这条路,你最好读一下 lot 关于使这种事情变得安全。您可能必须编写自己的同步上下文来保存此信息,并确保应用程序中的所有异步内容都会回显到此同步上下文。简而言之 - 不要这样做。真。这不值得。你会有很多难以复制的晦涩的错误,这是不值得的。