38

我在 .NET 4.5 中使用 async/await 模式在 WCF 中实现一些服务方法。示例服务:

合同:

[ServiceContract(Namespace = "http://async.test/")]
public interface IAsyncTest
{
    Task DoSomethingAsync();
}

执行:

MyAsyncService : IAsyncTest
{
    public async Task DoSomethingAsync()
    {
        var context = OperationContext.Current; // context is present

        await Task.Delay(10);

        context = OperationContext.Current; // context is null
    }
}

我遇到的问题是第一次await OperationContext.Current返回后null我无法访问OperationContext.Current.IncomingMessageHeaders.

在这个简单的示例中,这不是问题,因为我可以在await. 但在现实世界OperationContext.Current中,是从调用堆栈的深处访问的,我真的不想为了进一步传递上下文而更改大量代码。

有没有办法在await不手动将其向下传递的情况下获取操作上下文?

4

7 回答 7

29

不幸的是,这不起作用,我们将在未来的版本中看到修复。

同时,有一种方法可以将上下文重新应用到当前线程,这样您就不必传递对象:

    public async Task<double> Add(double n1, double n2)
    {

        OperationContext ctx = OperationContext.Current;

        await Task.Delay(100);

        using (new OperationContextScope(ctx))
        {
            DoSomethingElse();
        }
        return n1 + n2;
    }  

在上面的示例中,DoSomethingElse()方法将按预期访问 OperationContext.Current。

于 2012-10-16T16:42:02.803 回答
22

我认为您最好的选择是实际捕获它并手动传递它。您可能会发现这提高了代码的可测试性。

也就是说,还有其他几个选项:

  1. 将其添加到LogicalCallContext.
  2. 安装你自己的SynchronizationContext,它会OperationContext.Current在它执行时设置Post;这就是 ASP.NET 保留其HttpContext.Current.
  3. 安装你自己的TaskScheduler哪套OperationContext.Current

您可能还想在 Microsoft Connect 上提出此问题。

于 2012-10-09T11:16:28.007 回答
8

它似乎已在 .Net 4.6.2 中修复。看公告

于 2016-08-11T09:33:24.510 回答
4

扩展 Cleary 先生的 #1 选项,可以将以下代码放在 WCF 服务的构造函数中,以OperationContext在逻辑调用上下文中存储和检索 :

if (CallContext.LogicalGetData("WcfOperationContext") == null)
{
     CallContext.LogicalSetData("WcfOperationContext", OperationContext.Current);
}
else if (OperationContext.Current == null)
{
     OperationContext.Current = (OperationContext)CallContext.LogicalGetData("WcfOperationContext");
}

有了这个,任何你遇到空上下文问题的地方,你都可以编写如下内容:

var cachedOperationContext = CallContext.LogicalGetData("WcfOperationContext") as OperationContext;
var user = cachedOperationContext != null ? cachedOperationContext.ServiceSecurityContext.WindowsIdentity.Name : "No User Info Available";

免责声明:这是一年前的代码,我不记得我else if在构造函数中需要的原因,但它与异步有关,我知道在我的情况下需要它。

于 2014-10-08T13:01:15.310 回答
3

这是一个示例SynchronizationContext实现:

public class OperationContextSynchronizationContext : SynchronizationContext
{
    private readonly OperationContext context;

    public OperationContextSynchronizationContext(IClientChannel channel) : this(new OperationContext(channel)) { }

    public OperationContextSynchronizationContext(OperationContext context)
    {
        OperationContext.Current = context;
        this.context = context;
    }

    public override void Post(SendOrPostCallback d, object state)
    {
        OperationContext.Current = context;
        d(state);
    }
}

和用法:

var currentSynchronizationContext = SynchronizationContext.Current;
try
{
    SynchronizationContext.SetSynchronizationContext(new OperationContextSynchronizationContext(client.InnerChannel));
    var response = await client.RequestAsync();
    // safe to use OperationContext.Current here
}
finally
{
    SynchronizationContext.SetSynchronizationContext(currentSynchronizationContext);
}
于 2014-03-21T08:42:52.360 回答
2

对我们来说幸运的是,我们现实生活中的服务实现是通过UnityIoC 容器实例化的。这使我们能够创建一个IWcfOperationContext配置为具有 a 的 a PerResolveLifetimeManager,这仅意味着对于WcfOperationContext我们的每个实例只有一个RealService.
WcfOperationContext我们捕获的构造函数中OperationContext.Current,然后所有需要它的地方都从IWcfOperationContext. 这实际上是斯蒂芬克利里在他的回答中所建议的。

于 2012-10-28T12:39:16.810 回答
-2

更新:正如下面评论中所指出的,这个解决方案不是线程安全的,所以我想上面讨论的解决方案仍然是最好的方法。

我通过将 HttpContext 注册到我的 DI 容器 (Application_BeginRequest) 中来解决这个问题,并在需要时解决它。

登记:

this.UnityContainer.RegisterInstance<HttpContextBase>(new HttpContextWrapper(HttpContext.Current));

解决:

var context = Dependencies.ResolveInstance<HttpContextBase>();
于 2016-04-28T02:55:12.203 回答