Шаблон для вызова сервиса WCF, использующего асинхронный/ждущий

Я генерировал прокси с операциями на основе задач.

Как этот сервис должен быть вызван правильно (избавляющийся ServiceClient и OperationContext впоследствии) использование асинхронного/ждущего?

Моя первая попытка была:

public async Task GetHomeInfoAsync(DateTime timestamp)
{
    using (var helper = new ServiceHelper())
    {
        return await helper.Proxy.GetHomeInfoAsync(timestamp);
    }
}

Быть ServiceHelper класс, который создает ServiceClient и 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 является конкретным потоком. Если необходимо звонить, “ждут” для асинхронного вызова, используют его за пределами блока OperationContextScope.

Таким образом, это - проблема! Но, как мы фиксируем его правильно?

Этот парень сделал, что заявляет MSDN:

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

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

Моя проблема с его кодом, то, что он никогда не звонит Близко (или Аварийное прекращение работы) на ServiceClient.

Я также нашел способ распространить OperationContextScope использование пользовательского SynchronizationContext. Но, помимо того, что это - много "опасного" кода, он заявляет что:

Стоит отметить, что это действительно имеет несколько маленьких проблем относительно избавления от объемов операционного контекста (так как они только позволяют Вам располагать их на вызывающем потоке), но это, кажется, не проблема с тех пор (по крайней мере, согласно дизассемблированию), они реализуют, Располагают (), но не Завершают ().

Так, мы не повезло здесь? Есть ли доказанный шаблон для вызова сервисов WCF, использующих асинхронный/ждущий И избавляющихся от ОБОИХ ServiceClient и OperationContextScope? Возможно, кто-то формирует Microsoft (возможно, гуру Stephen Toub :)) может помочь.

Спасибо!

[ОБНОВЛЕНИЕ]

С большим количеством справки от пользователя Noseratio я придумал что-то, что работает: не использовать OperationContextScope. При использовании его по какой-либо из этих причин попытайтесь найти обходное решение, которое соответствует сценарию. Иначе, если Вы действительно, действительно, потребность OperationContextScope, необходимо будет придумать реализацию a SynchronizationContext это получает его, и это кажется очень твердым (если вообще возможный - должна быть причина, почему это не поведение по умолчанию).

Так, полный рабочий код:

public async Task GetHomeInfoAsync(DateTime timestamp)
{
    using (var helper = new ServiceHelper())
    {
        return await helper.Proxy.GetHomeInfoAsync(timestamp);
    }
}

С ServiceHelper быть:

public class ServiceHelper : IDisposable
    where TServiceClient : ClientBase, 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 заявляет.

58
задан 23 May 2017 в 15:25

1 ответ

Это было некоторое время на этом, но я соглашусь со своим собственным домашней выпечки решением.

, Если Вы не возражаете обходиться без OperationContextScope, можно было бы рассмотреть что-то вдоль этих строк:

Дополнительные методы

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;

namespace Intexx.ServiceModel
{
    public static class WcfExtensions
    {
        [DebuggerStepThrough]
        public static void Call<TChannel>(this TChannel Client, Action<TChannel> Method) where TChannel : ICommunicationObject
        {
            try
            {
                Method.Invoke(Client);
            }
            finally
            {
                Cleanup(Client);
            }
        }

        [DebuggerStepThrough]
        public static TResult Call<TChannel, TResult>(this TChannel Client, Func<TChannel, TResult> Method) where TChannel : ICommunicationObject
        {
            try
            {
                return Method.Invoke(Client);
            }
            finally
            {
                Cleanup(Client);
            }
        }

        [DebuggerStepThrough]
        public async static Task CallAsync<TChannel>(this TChannel Client, Func<TChannel, Task> Method) where TChannel : ICommunicationObject
        {
            try
            {
                await Method.Invoke(Client);
            }
            finally
            {
                Cleanup(Client);
            }
        }

        [DebuggerStepThrough]
        public async static Task<TResult> CallAsync<TChannel, TResult>(this TChannel Client, Func<TChannel, Task<TResult>> Method) where TChannel : ICommunicationObject
        {
            try
            {
                return await Method.Invoke(Client);
            }
            finally
            {
                Cleanup(Client);
            }
        }

        private static void Cleanup<TChannel>(TChannel Client) where TChannel : ICommunicationObject
        {
            try
            {
                if (Client.IsNotNull)
                {
                    if (Client.State == CommunicationState.Faulted)
                        Client.Abort();
                    else
                        Client.Close();
                }
            }
            catch (Exception ex)
            {
                Client.Abort();

                if (!ex is CommunicationException && !ex is TimeoutException)
                    throw new Exception(ex.Message, ex);
            }

            finally
            {
                Client = null;
            }
        }
    }
}

Клиентский класс

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;

namespace Reader
{
    public class Client
    {
        public static CemReaderClient Create()
        {
            Tuple<Channels.Binding, EndpointAddress, double> oService;

            try
            {
                oService = Main.Services(typeof(ICemReader));
                return new CemReaderClient(oService.Item1, oService.Item2);
            }
            catch (KeyNotFoundException ex)
            {
                return null;
            }
        }
    }
}

Использование (в VB, поскольку код не преобразовал бы)

Using oReader As Reader.CemReaderClient = Reader.Client.Create
  If oReader.IsNotNothing Then
    Dim lIsReading = Await oReader.CallAsync(Function(Reader As Reader.CemReaderClient)
                                               Me.ConfigFilePath = If(Me.ConfigFilePath, Reader.GetConfigFilePath)
                                               Me.BackupDrive = If(Me.BackupDrive, Reader.GetBackupDrive)
                                               Me.SerialPort = If(Me.SerialPort, Reader.GetSerialPort)
                                               Me.LogFolder = If(Me.LogFolder, Reader.GetLogFolder)

                                               Return Reader.GetIsReadingAsync
                                             End Function)
  End If
End Using

у меня было это выполнение надежно в производстве при загрузках частоты приблизительно 15 вызовов/секунда на стороне клиента (это - то, с такой скоростью, как последовательная обработка позволила бы). Это было на единственном потоке, though—, это не было строго протестировано на потокобезопасность. YMMV.

В моем случае, я решил прокрутить дополнительные методы в их собственный частный пакет NuGet. Целая конструкция оказалась довольно удобной.

Это должно будет быть переоценено, конечно, если OperationContextScope когда-нибудь заканчивает тем, что был необходим.

бит с Tuple в Client класс для Сервисной поддержки Исследования. Если кто-либо хотел бы видеть, что код также, дайте крик, и я обновлю свой ответ.

1
ответ дан 1 November 2019 в 15:06

Другие вопросы по тегам:

Похожие вопросы: