在WCF服务中使用async / await时,在第一次等待之后,OperationContext.Current为null

时间:2012-10-09 09:40:11

标签: .net wcf .net-4.5 async-await

我在.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点之后获取操作上下文而不是手动将其传递给堆栈?

7 个答案:

答案 0 :(得分:28)

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

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

    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。

答案 1 :(得分:21)

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

尽管如此,还有其他几种选择:

  1. 将其添加到LogicalCallContext
  2. 安装自己的SynchronizationContextOperationContext.Current时会设置Post;这就是ASP.NET保留其HttpContext.Current
  3. 的方式
  4. 安装自己设置TaskScheduler的{​​{1}}。
  5. 您可能还想在Microsoft Connect上引发此问题。

答案 2 :(得分:7)

似乎是在.Net 4.6.2中修复的。见the announcement

答案 3 :(得分: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);
}

答案 4 :(得分:3)

扩展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的原因,但它与异步有关,我知道在我的情况下需要它

答案 5 :(得分:2)

幸运的是,我们的实际服务实现通过Unity IoC容器进行实例化。这使我们能够创建IWcfOperationContext,其中PerResolveLifetimeManager配置为WcfOperationContext,这意味着我们RealService的每个实例只会有WcfOperationContext个实例。<登记/> 在OperationContext.Current的构造函数中,我们捕获IWcfOperationContext,然后所有需要它的地方从{{1}}获取它。这实际上是Stephen Cleary在答案中提出的建议。

答案 6 :(得分:-2)

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

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

寄存器:

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

消退:

var context = Dependencies.ResolveInstance<HttpContextBase>();