小编典典

使用async / await调用WCF服务的模式

c#

我使用基于任务的操作生成了一个代理。

应该如何使用async / await 正确调用此服务(处置ServiceClientOperationContext之后)?

我的第一次尝试是:

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

作为ServiceHelper创建the ServiceClient和the OperationContextScope并随后对其进行处理的类:

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被放置在与其创建的线程不同的线程上。”

MSDN说:

不要在OperationContextScope块中使用异步“等待”模式。发生延续时,它可以在其他线程上运行,并且OperationContextScope是特定于线程的。如果您需要为异步调用调用“
await”,请在OperationContextScope块之外使用它。

这就是问题所在!但是,我们如何正确修复它?

这个家伙正是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);
  }
}

我的代码存在问题,就是他从未在ServiceClient上调用Close(或中止)。

我还找到了一种OperationContextScope使用自定义进行传播的方法SynchronizationContext。但是,除了有很多“风险”代码这一事实之外,他还指出:

值得注意的是,关于操作上下文作用域的处置确实存在一些小问题(因为它们仅允许您将它们处置在调用线程上),但这似乎不是问题,因为(至少根据反汇编),它们实现Dispose()而不是Finalize()。

那么,我们在这里不走运吗?是否存在使用async / await并同时处理the ServiceClient和the
来调用WCF服务的行之有效的模式OperationContextScope?也许来自Microsoft(也许是Stephen
Toub大师)的人可以提供帮助。

谢谢!

[更新]

在Noseratio用户的大量帮助下,我想到了一个可行的方法:不要使用OperationContextScope。如果出于任何这些原因使用它,请尝试查找适合您情况的解决方法。否则,如果确实需要OperationContextScope,则必须提出一个SynchronizationContext捕获它的实现,这似乎很难(如果有可能,这一定是为什么这不是默认行为的原因) )。

因此,完整的工作代码是:

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;
            }
        }
    }
}

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

唯一可能的“陷阱”是在中GetHomeInfoAsync,您不能只返回Task从代理获得的信息(这看起来很自然,为什么要Task在已有代理的情况下创建一个新代理)。好吧,在这种情况下,您需要await使用代理Task
然后 关闭(或中止)ServiceClient,否则调用服务后(在通过有线发送字节的同时)立即关闭它!

好的,我们有办法使它起作用,但是如Noseratio所言,最好是从权威来源获得答案。


阅读 359

收藏
2020-05-19

共1个答案

小编典典

我认为可行的解决方案可能是使用 自定义等待程序 来通过 传递
新的操作上下文OperationContext.Current。本身的实现OperationContext似乎不需要线程关联。这是模式:

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);
    }
}

下面是执行FlowingOperationContextScopeContinueOnScope(仅略测试):

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);
        }
    }
}
2020-05-19