【问题标题】:How to get access to application context from the thread that was created via Task.Factory.StartNew如何从通过 Task.Factory.StartNew 创建的线程访问应用程序上下文
【发布时间】:2014-02-15 15:45:50
【问题描述】:

我有一个在主应用程序线程中运行的方法,但会为长时间运行的操作创建新的 Task,而 awaits 是它的结果。

public async Task<CalculatorOutputModel> CalculateXml(CalculatorInputModel input)
{
    // 1
    return await Task.Factory.StartNew(
        new Func<CalculatorOutputModel> (() => 
        {
            // 2
            using (var ms = Serializer.Serialize(input))
            {
                ms.Position = 0;
                using (var rawResult = Channel.RawGetFrbXmlOutputs(ms)) // 3
                {
                    var result = Parser.Parse(rawResult);
                    return result;
                }
            }
        }),
        CancellationToken.None,
        TaskCreationOptions.None,
        TaskScheduler.FromCurrentSynchronizationContext());
}

我的问题是第 (1) 点和 (2) 点中的 AppContext “有点”不同。在第 1 点中,Current 属性中有通常的应用程序上下文,其中包含我需要的所有数据。在第 2 点,如您所知,AppContext.Current 中有 null 引用,因此我无法访问任何数据。第 2 点中访问上下文的问题似乎很简单,只需在局部变量中“捕获”当前上下文或将其作为参数传递即可。对我来说这个问题更加困难,因为我需要在标记为“3”的行的深处访问上下文。
类本身派生自System.ServiceModel.ClientBase&lt;TChannel&gt;,而我需要访问上下文的地方是实现IClientMessageInspector 的类。

class CalculatorMessageInspector : IClientMessageInspector
{
    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        if (AppContext.Current != null)
        {
            // never goes here
        }
    }
}

澄清一下,这里是调用堆栈(我不能在所需的方法中传递我的上下文):

所以:

  1. 我无法将上下文传递给Channel 的方法,因为它没有任何意义;
  2. 我无法从Channel 的上下文中保存所需参数,因为它是代理类;
  3. 我无法在CalculatorMessageInspector 中保存所需的参数,因为它是在当前上下文已经为空的地方创建的。

任何人都可以建议我如何在另一个线程中保持在同一上下文中的任何方法吗?或者,至少,如何从方法层次结构中标记为“2”的位置传递参数?也许,我可以使用SynchronizationContext 以某种方式实现它?非常感谢您的任何建议。

更新

AppContext.Current 认为与HttpContext.Current != null ? HttpContext.Current.Items[AppContextKey] : null 相同

更新 2 因此,在这种情况下,似乎没有通用的解决方案来持久化上下文。因此,在这种具体情况下(非常具体)唯一适用的解决方案是使用闭包捕获所需的参数,然后保存在一个对象中,该对象将在所需的方法中可用(对我来说,向IEndpointBehavior 的实现者添加属性,但该解决方案有点奇怪)。因此,最适用的解决方案是丢弃同步调用的异步包装,因此AppContext 永远不会“消失”。标记斯蒂芬的答案是正确的。

【问题讨论】:

  • 你不能传递上下文,但你能改变方法签名吗?如果可以,那么您可以将委托传递给 BeforeSendRequest(),然后从可以访问当前 AppContext 的代码块中执行该委托中的一些代码。
  • 什么是AppContext
  • 只是一个评论,你应该使用Task.Runasync-await 就像斯蒂芬在这里解释:blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
  • @DavidKhaykin 我无法更改 BeforeSendRequest 签名,因为这是IClientMessageInspector 接口的成员。因此,不幸的是,情况并非如此。无论如何谢谢;)
  • @StephenCleary 这点很好,我错过了。 AppContext.Current 是我们内部类的静态 get-ter,但查看源代码表明,在所描述的情况下,这等效于 System.Web.HttpContext.Current.Items[AppContextKey]。所以它可以在我的问题中用HttpContext.Current 替换AppContext.Current

标签: c# wcf async-await c#-5.0 applicationcontext


【解决方案1】:

要在 ASP.NET 上使用 asyncawait,您必须以 .NET 4.5 为目标并关闭 quirks 模式。最好的方法是在 web.config 中设置 /configuration/system.web/httpRuntime@targetFramework="4.5"

此外,您不应在 ASP.NET 上使用 Task.RunTask.Factory.StartNew。所以你的代码应该看起来更像这样:

public CalculatorOutputModel CalculateXml(CalculatorInputModel input)
{
    using (var ms = Serializer.Serialize(input))
    {
        ms.Position = 0;
        using (var rawResult = Channel.RawGetFrbXmlOutputs(ms))
        {
            var result = Parser.Parse(rawResult);
            return result;
        }
    }
}

【讨论】:

  • 这正是我的代码前一段时间的样子。但在这种情况下,using 语句中的代码块会挂起线程几秒钟,因为调用此通道非常耗时。这就是我决定在另一个线程中执行此操作的主要原因。在我的示例中,预计调用此方法的主线程会继续准备其他请求并同时发出它们。我错过了什么吗?
  • 首先,ASP.NET 天生就是多线程的。因此,除非您的代码正在做一些非常奇怪的事情,否则其他请求可以由其他线程处理。其次,如果你想使用async/await,那么你需要异步调用实际的通道,而不是在另一个线程中包装同步调用。即,如果 Channel 是 WCF 代理,则可以将 using 语句更改为 using (var rawResult = await Channel.RawGetFrbXmlOutputsAsync(ms))
  • @Eadel 阅读此 (curah.microsoft.com/44400/async-and-aspnet) 以了解线程在 ASP.NET 中的工作原理以及它如何与 async-await 一起工作。
猜你喜欢
  • 2017-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-16
相关资源
最近更新 更多