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

.net 在WCF服务中使用async/await时,OperationContext.Current在首次等待后为null,.net,wcf,.net-4.5,async-await,.net,Wcf,.net 4.5,Async Await,我正在使用.NET4.5中的async/await模式在WCF中实现一些服务方法。 示例服务: 合同: [ServiceContract(Namespace = "http://async.test/")] public interface IAsyncTest { Task DoSomethingAsync(); } 实施: MyAsyncService : IAsyncTest { public async Task DoSomethingAsync() {

我正在使用.NET4.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
    }
}
我遇到的问题是,在第一次
之后等待
操作上下文。当前
返回
null
,我无法访问
操作上下文。当前.IncomingMessageHeaders

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


有没有一种方法可以在
wait
点之后获取操作上下文,而不用手动将其传递到堆栈中?

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

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

  • 将其添加到
    LogicalCallContext
  • 安装您自己的
    SynchronizationContext
    ,它将在执行
    Post
    时设置
    OperationContext.Current
    ;这就是ASP.NET如何保存其
    HttpContext.Current
  • 安装您自己的
    TaskScheduler
    ,它设置
    OperationContext.Current

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

    不幸的是,此问题无法解决,我们将在将来的版本中考虑解决此问题

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

        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;
        }  
    
    公共异步任务添加(双n1,双n2)
    {
    OperationContext ctx=OperationContext.Current;
    等待任务。延迟(100);
    使用(新OperationContextScope(ctx))
    {
    DoSomethingElse();
    }
    返回n1+n2;
    }  
    

    在上面的示例中,DoSomethingElse()方法可以访问OperationContext.Current,正如预期的那样。

    幸运的是,我们的实际服务实现是通过
    Unity
    IoC容器实例化的。这使我们能够创建一个
    IWCOperationContext
    ,它被配置为具有
    PerResolveLifetimeManager
    ,这意味着对于我们的
    RealService
    的每个实例,只有一个
    wcfoOperationContext
    实例

    wcfooperationcontext
    的构造函数中,我们捕获
    OperationContext.Current
    ,然后从
    iwcfooperationcontext
    获取所有需要它的位置。这实际上是Stephen Cleary在他的回答中建议的。

    下面是一个示例
    同步上下文
    实现:

    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);
    }
    
    扩展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的原因,但这与async有关,我知道在我的情况下需要它。

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

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

    登记册:

    this.UnityContainer.RegisterInstance<HttpContextBase>(new HttpContextWrapper(HttpContext.Current));
    
    this.UnityContainer.RegisterInstance(新的HttpContextWrapper(HttpContext.Current));
    
    决心:

    var context = Dependencies.ResolveInstance<HttpContextBase>();
    
    var context=Dependencies.ResolveInstance();
    
    它似乎在.Net 4.6.2中得到了修复。请参见

    任务
    实例通过连线序列化到客户端意味着什么?这很有趣。不过,我不确定您是否真的希望以这种方式执行操作。当操作因某种原因失败时,您会怎么做?客户认为它成功了。您最好在某种类型的事务队列中对这些操作进行排队。@Steven:WCF运行时在
    任务完成之前不会向客户端返回响应。@chrismaric感谢您关于在WCF服务中不使用异步的评论。在这里解决了我的问题:@MikeTaverne是的,这正是不在内部使用异步的原因。处理线程切换丢失的范围和上下文是疯狂的。Jon-你显然比大多数代表16岁的人知道得多(我在MSDN上找到了你的博客)。我可以建议您更新您的个人资料,以便人们了解您的答案的质量。Jon,我们只是想确定一种解决此问题的方法,我想知道您是否可以解释为什么在上面的代码中使用OperationContextScope,而不是直接使用ctx?(我在这里发布了更多详细信息)使用OperationContextScope的主要原因是将OperationContext.Current设置为传递给构造函数的设置。这可以防止您将ctx实例向下传递到深层调用堆栈(例如,您不必修改方法签名以获取OperationContext参数)。此解决方案不允许从等待中访问OperationContext.Current。在上面的例子中,是的,它是有效的。凯文是对的。当调用位于await方法内时,此解决方案没有帮助。如果存在嵌套级别的等待调用,则不会替换当前SynchronizationContext。因此,我必须通过检查扩展重写的Post方法,如果SynchronizationContext.Current为null,我将调用SynchronizationContext.SetSynchronizationContext(此)。我不知道这是否是正确的方法,但它对我来说是有效的。我怀疑这是否正确