我正在尝试在我的应用程序中实现一些异步代码。我开始一个新的任务,不要等待结果。此任务已经开始新闻另一个任务等待结果。第二个任务使用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为空的原因。
我能做些什么来正确解决这个问题吗?
答案 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.Run
,await
,ContinueWith
或其他任何方式创建更多任务,您就会失去这种情境。同样重要的是,没有地方可以清除这些信息 - 这显然是一个巨大的安全漏洞,因为并发请求可能会使代码的不同部分执行不同的用户上下文。如果你选择走这条路,你最好读一下 lot 关于使这种事情变得安全。您可能必须编写自己的同步上下文来保存此信息,并确保应用程序中的所有异步内容都会回显到此同步上下文。简而言之 - 不要这样做。真。这不值得。你会有很多难以复制的晦涩的错误,这是不值得的。