使用异步调用WCF服务模式/待机 [英] Pattern for calling WCF service using async/await

查看:270
本文介绍了使用异步调用WCF服务模式/待机的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我产生了代理基于任务的操作

应如何服务正确(处置的 ServiceClient 的OperationContext 之后)使用异步/计谋调用?

How should this service be invoked properly (disposing of the ServiceClient and the OperationContext afterwards) using async/await?

我的第一个尝试是:

public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp)
{
    using (var helper = new ServiceHelper<ServiceClient, ServiceContract>())
    {
        return await helper.Proxy.GetHomeInfoAsync(timestamp);
    }
}

存在 ServiceHelper 这将创建一个类 ServiceClient OperationContextScope ,其中处置算账:

Being ServiceHelper a class which creates the ServiceClient and the OperationContextScope and disposes of them afterwards:

try
{
    if (_operationContextScope != null)
    {
        _operationContextScope.Dispose();
    }

    if (_serviceClient != null)
    {
        if (_serviceClient.State != CommunicationState.Faulted)
        {
            _serviceClient.Close();
        }
        else
        {
            _serviceClient.Abort();
        }
    }
}
catch (CommunicationException)
{
    _serviceClient.Abort();
}
catch (TimeoutException)
{
    _serviceClient.Abort();
}
catch (Exception)
{
    _serviceClient.Abort();
    throw;
}
finally
{
    _operationContextScope = null;
    _serviceClient = null;
}

不过,这遭到惨败调用两个服务的同时,出现以下错误的时候:这OperationContextScope被设置在比它创建了一个不同的线程。

However, this failed miserably when calling two services at the same time with the following error: "This OperationContextScope is being disposed on a different thread than it was created."

<一个href="http://msdn.microsoft.com/en-us/library/system.servicemodel.operationcontextscope.aspx">MSDN说:

不要使用异步等待一个OperationContextScope块中的模式。当持续发生时,它可以运行在不同的线程和OperationContextScope是线程特定的。如果您需要调用等待一个异步调用,使用它OperationContextScope块外。

Do not use the asynchronous "await" pattern within a OperationContextScope block. When the continuation occurs, it may run on a different thread and OperationContextScope is thread specific. If you need to call "await" for an async call, use it outside of the OperationContextScope block.

所以,这就是问题所在!但是,我们如何解决它是否正确?

So that's the problem! But, how do we fix it properly?

这家伙的确正是MSDN说

private async void DoStuffWithDoc(string docId)
{
   var doc = await GetDocumentAsync(docId);
   if (doc.YadaYada)
   {
        // more code here
   }
}

public Task<Document> GetDocumentAsync(string docId)
{
  var docClient = CreateDocumentServiceClient();
  using (new OperationContextScope(docClient.InnerChannel))
  {
    return docClient.GetDocumentAsync(docId);
  }
}

我的问题与他的code,是他从来没有呼吁ServiceClient关闭(或终止)。

My problem with his code, is that he never calls Close (or Abort) on the ServiceClient.

我还发现传播 > OperationContextScope 使用自定义的的SynchronizationContext 。但是,除了事实,这是一个很大的风险code,他说:

I also found a way of propagating the OperationContextScope using a custom SynchronizationContext. But, besides the fact that it's a lot of "risky" code, he states that:

值得一提的是,它确实有关于处置操作上下文范围(因为它们只允许你处理它们调用线程上的)一些小问题,但这似乎不是,因为一个问题(在至少根据拆卸),它们实现Dispose(),而不是最终确定()。

It’s worth noting that it does have a few small issues regarding the disposal of operation-context scopes (since they only allow you to dispose them on the calling thread), but this doesn’t seem to be an issue since (at least according to the disassembly), they implement Dispose() but not Finalize().

那么,我们的运气在这里的?有没有一个成熟的模式,使用调用WCF服务异步/等待和处理双方的 ServiceClient OperationContextScope ?也许有人形成微软(或许是大师斯蒂芬Toub :))可以提供帮助。

So, are we out of luck here? Is there a proven pattern for calling WCF services using async/await AND disposing of BOTH the ServiceClient and the OperationContextScope? Maybe someone form Microsoft (perhaps guru Stephen Toub :)) can help.

谢谢!

[更新]

通过大量的来自用户Noseratio帮助下,我想出了一些作品:不使用 OperationContextScope 。如果你正在使用它的任何<一href="http://stackoverflow.com/questions/2470639/when-should-i-use-operationcontextscope-inside-of-a-wcf-service">these原因,试图找到适合您的方案一种解决方法。否则,如果你真的,真的,需要 OperationContextScope ,你就必须拿出一个实施的SynchronizationContext 捕获它,这似乎很难(如果可能的话 - 有一定是有原因的,这是不是默认的行为)。

With a lot of help from user Noseratio, I came up with something that works: do not use OperationContextScope. If you are using it for any of these reasons, try to find a workaround that fits your scenario. Otherwise, if you really, really, need OperationContextScope, you'll have to come up with an implementation of a SynchronizationContext that captures it, and that seems very hard (if at all possible - there must be a reason why this isn't the default behavior).

所以,完整的工作code是:

So, the full working code is:

public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp)
{
    using (var helper = new ServiceHelper<ServiceClient, ServiceContract>())
    {
        return await helper.Proxy.GetHomeInfoAsync(timestamp);
    }
}

使用 ServiceHelper 之中:

public class ServiceHelper<TServiceClient, TService> : IDisposable
    where TServiceClient : ClientBase<TService>, new()
    where TService : class
{
protected bool _isInitialized;
    protected TServiceClient _serviceClient;

    public TServiceClient Proxy
    {
        get
        {
            if (!_isInitialized)
            {
                Initialize();
                _isInitialized = true;
            }
            else if (_serviceClient == null)
            {
                throw new ObjectDisposedException("ServiceHelper");
            }

            return _serviceClient;
        }
    }

    protected virtual void Initialize()
    {
        _serviceClient = new TServiceClient();
    }

    // Implement IDisposable.
    // Do not make this method virtual.
    // A derived class should not be able to override this method.
    public void Dispose()
    {
        Dispose(true);

        // Take yourself off the Finalization queue 
        // to prevent finalization code for this object
        // from executing a second time.
        GC.SuppressFinalize(this);
    }

    // Dispose(bool disposing) executes in two distinct scenarios.
    // If disposing equals true, the method has been called directly
    // or indirectly by a user's code. Managed and unmanaged resources
    // can be disposed.
    // If disposing equals false, the method has been called by the 
    // runtime from inside the finalizer and you should not reference 
    // other objects. Only unmanaged resources can be disposed.
    protected virtual void Dispose(bool disposing)
    {
        // If disposing equals true, dispose all managed 
        // and unmanaged resources.
        if (disposing)
        {
            try
            {
                if (_serviceClient != null)
                {
                    if (_serviceClient.State != CommunicationState.Faulted)
                    {
                        _serviceClient.Close();
                    }
                    else
                    {
                        _serviceClient.Abort();
                    }
                }
            }
            catch (CommunicationException)
            {
                _serviceClient.Abort();
            }
            catch (TimeoutException)
            {
                _serviceClient.Abort();
            }
            catch (Exception)
            {
                _serviceClient.Abort();
                throw;
            }
            finally
            {
                _serviceClient = null;
            }
        }
    }
}

请注意,该类支持扩展;也许你需要继承并提供凭据。

Note that the class supports extension; perhaps you need to inherit and provide credentials.

唯一可能的疑难杂症的是,在 GetHomeInfoAsync ,你不能只返回工作你从代理(这应该很自然的,为什么要创建一个新的工作时,你已经有一个)。那么,在这种情况下,你需要等待代理工作然后的接近(或中止)的 ServiceClient ,否则你会关闭它调用服务(而字节正在通过网络发送)!后马上

The only possible "gotcha" is that in GetHomeInfoAsync, you can't just return the Task you get from the proxy (which should seem natural, why create a new Task when you already have one). Well, in this case you need to await the proxy Task and then close (or abort) the ServiceClient, otherwise you'll be closing it right away after invoking the service (while bytes are being sent over the wire)!

OK,我们有办法让它工作,但它会是不错的得到答案从权威人士透露,作为Noseratio状态。

OK, we have a way to make it work, but it'd be nice to get an answer from an authoritative source, as Noseratio states.

推荐答案

我觉得一个可行的解决方案可能是使用自定义awaiter 通过流动的新操作环境的OperationContext .Current 。该<一href="http://referencesource.microsoft.com/#System.ServiceModel/ServiceModel/System/ServiceModel/OperationContext.cs#6f1688522ae66eea">implementation对的OperationContext 本身似乎并没有要求线程关联。下面是模式:

I think a feasible solution might be to use a custom awaiter to flow the new operation context via OperationContext.Current. The implementation of OperationContext itself doesn't appear to require thread affinity. Here is the pattern:

async Task TestAsync()
{
    using(var client = new WcfAPM.ServiceClient())
    using (var scope = new FlowingOperationContextScope(client.InnerChannel))
    {
        await client.SomeMethodAsync(1).ContinueOnScope(scope);
        await client.AnotherMethodAsync(2).ContinueOnScope(scope);
    }
}

下面是实施 FlowingOperationContextScope ContinueOnScope (仅略测试):

Here is the implementation of FlowingOperationContextScope and ContinueOnScope (only slightly tested):

public sealed class FlowingOperationContextScope : IDisposable
{
    bool _inflight = false;
    bool _disposed;
    OperationContext _thisContext = null;
    OperationContext _originalContext = null;

    public FlowingOperationContextScope(IContextChannel channel):
        this(new OperationContext(channel))
    {
    }

    public FlowingOperationContextScope(OperationContext context)
    {
        _originalContext = OperationContext.Current;
        OperationContext.Current = _thisContext = context;
    }

    public void Dispose()
    {
        if (!_disposed)
        {
            if (_inflight || OperationContext.Current != _thisContext)
                throw new InvalidOperationException();
            _disposed = true;
            OperationContext.Current = _originalContext;
            _thisContext = null;
            _originalContext = null;
        }
    }

    internal void BeforeAwait()
    {
        if (_inflight)
            return;
        _inflight = true;
        // leave _thisContext as the current context
   }

    internal void AfterAwait()
    {
        if (!_inflight)
            throw new InvalidOperationException();
        _inflight = false;
        // ignore the current context, restore _thisContext
        OperationContext.Current = _thisContext;
    }
}

// ContinueOnScope extension
public static class TaskExt
{
    public static SimpleAwaiter<TResult> ContinueOnScope<TResult>(this Task<TResult> @this, FlowingOperationContextScope scope)
    {
        return new SimpleAwaiter<TResult>(@this, scope.BeforeAwait, scope.AfterAwait);
    }

    // awaiter
    public class SimpleAwaiter<TResult> :
        System.Runtime.CompilerServices.INotifyCompletion
    {
        readonly Task<TResult> _task;

        readonly Action _beforeAwait;
        readonly Action _afterAwait;

        public SimpleAwaiter(Task<TResult> task, Action beforeAwait, Action afterAwait)
        {
            _task = task;
            _beforeAwait = beforeAwait;
            _afterAwait = afterAwait;
        }

        public SimpleAwaiter<TResult> GetAwaiter()
        {
            return this;
        }

        public bool IsCompleted
        {
            get 
            {
                // don't do anything if the task completed synchronously
                // (we're on the same thread)
                if (_task.IsCompleted)
                    return true;
                _beforeAwait();
                return false;
            }

        }

        public TResult GetResult()
        {
            return _task.Result;
        }

        // INotifyCompletion
        public void OnCompleted(Action continuation)
        {
            _task.ContinueWith(task =>
            {
                _afterAwait();
                continuation();
            },
            CancellationToken.None,
            TaskContinuationOptions.ExecuteSynchronously,
            SynchronizationContext.Current != null ?
                TaskScheduler.FromCurrentSynchronizationContext() :
                TaskScheduler.Current);
        }
    }
}

这篇关于使用异步调用WCF服务模式/待机的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆