Асинхронные операции внутри асинхронной операции

StackOverflow https://stackoverflow.com/questions/763417

Вопрос

Мои знания многопоточности все еще довольно элементарные, поэтому был бы очень признателен за некоторые советы.У меня есть интерфейс IOperationInvoker (из WCF), который имеет следующие методы:

IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)

Учитывая конкретную реализацию этого интерфейса, мне нужно реализовать тот же интерфейс, вызывая базовую реализацию в отдельном потоке.(если вам интересно, почему конкретная реализация вызывает устаревший COM-объект, который должен находиться в другом состоянии подразделения).

На данный момент я делаю что-то вроде этого:

public StaOperationSyncInvoker : IOperationInvoker {
   IOperationInvoker _innerInvoker;
   public StaOperationSyncInvoker(IOperationInvoker invoker) {
       this._innerInvoker = invoker;
   } 


    public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
    {
        Thread t = new Thread(BeginInvokeDelegate);
        InvokeDelegateArgs ida = new InvokeDelegateArgs(_innerInvoker, instance, inputs, callback, state);
        t.SetApartmentState(ApartmentState.STA);
        t.Start(ida);
        // would do t.Join() if doing syncronously
        // how to wait to get IAsyncResult?
        return ida.AsyncResult;
    }

    public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
    {
        // how to call invoke end on the 
        // thread? could we have wrapped IAsyncResult
        // to get a reference here?
        return null;
    }

    private class InvokeDelegateArgs {
        public InvokeDelegateArgs(IOperationInvoker invoker, object instance, object[] inputs, AsyncCallback callback, object state)
        {
            this.Invoker = invoker;
            this.Instance = instance;
            this.Inputs = inputs;
            this.Callback = callback;
            this.State = state;
        }

        public IOperationInvoker Invoker { get; private set; }
        public object Instance { get; private set; }
        public AsyncCallback Callback { get; private set; }
        public IAsyncResult AsyncResult { get; set; }
        public Object[] Inputs { get; private set; }
        public Object State { get; private set; }
    }
    private static void BeginInvokeDelegate(object data)
    {
        InvokeDelegateArgs ida = (InvokeDelegateArgs)data;
        ida.AsyncResult = ida.Invoker.InvokeBegin(ida.Instance, ida.Inputs, ida.Callback, ida.State);
    }
}

Я думаю, мне нужно обернуть возвращенный AsyncResult своим собственным, чтобы я мог вернуться к потоку, который мы накачали...но, честно говоря, я немного не в себе.Есть какие-нибудь указатели?

Большое спасибо,

Джеймс

Это было полезно?

Решение

Самый простой способ асинхронной реализации синхронного метода — поместить его в делегат и использовать метод BeginInvoke и EndInvoke методы результирующего делегата.Это запустит синхронный метод в потоке пула потоков и BeginInvoke вернет IAsyncResult реализацию, поэтому вам не нужно реализовывать ее всю суть.Однако вам нужно переправить немного дополнительных данных в IAsyncResult возвращенный IOperationInvoker.InvokeEnd.Вы могли бы сделать это легко, создав реализацию IAsyncResult который делегирует все внутреннему IAsyncResult, но имеет дополнительное поле для хранения делегата, поэтому при IAsyncResult экземпляр передается InvokeEnd, вы можете получить доступ к делегату для вызова EndInvoke в теме.

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

Что вам нужно сделать, так это правильно реализовать IAsyncResult.Из этого следует почти все, поскольку IAsyncResult будет содержать все биты, необходимые для синхронизации.

Вот очень простая, но не очень эффективная реализация IAsyncResult.Он включает в себя все основные функции:передача аргументов, событие синхронизации, реализация обратного вызова, распространение исключений из асинхронной задачи и возврат результата.

using System;
using System.Threading;

class MyAsyncResult : IAsyncResult
{
    object _state;
    object _lock = new object();
    ManualResetEvent _doneEvent = new ManualResetEvent(false);
    AsyncCallback _callback;
    Exception _ex;
    bool _done;
    int _result;
    int _x;

    public MyAsyncResult(int x, AsyncCallback callback, object state)
    {
        _callback = callback;
        _state = state;
        _x = x; // arbitrary argument(s)
    }

    public int X { get { return _x; } }

    public void SignalDone(int result)
    {
        lock (_lock)
        {
            _result = result;
            _done = true;
            _doneEvent.Set();
        }
        // never invoke any delegate while holding a lock
        if (_callback != null)
            _callback(this); 
    }

    public void SignalException(Exception ex)
    {
        lock (_lock)
        {
            _ex = ex;
            _done = true;
            _doneEvent.Set();
        }
        if (_callback != null)
            _callback(this);
    }

    public object AsyncState
    {
        get { return _state; }
    }

    public WaitHandle AsyncWaitHandle
    {
        get { return _doneEvent; }
    }

    public bool CompletedSynchronously
    {
        get { return false; }
    }

    public int Result
    {
        // lock (or volatile, complex to explain) needed
        // for memory model problems.
        get
        {
            lock (_lock)
            {
                if (_ex != null)
                    throw _ex;
                return _result;
            }
        }
    }

    public bool IsCompleted
    {
        get { lock (_lock) return _done; }
    }
}

class Program
{
    static void MyTask(object param)
    {
        MyAsyncResult ar = (MyAsyncResult) param;
        try
        {
            int x = ar.X;
            Thread.Sleep(1000); // simulate lengthy work
            ar.SignalDone(x * 2); // demo work = double X
        }
        catch (Exception ex)
        {
            ar.SignalException(ex);
        }
    }

    static IAsyncResult Begin(int x, AsyncCallback callback, object state)
    {
        Thread th = new Thread(MyTask);
        MyAsyncResult ar = new MyAsyncResult(x, callback, state);
        th.Start(ar);
        return ar;
    }

    static int End(IAsyncResult ar)
    {
        MyAsyncResult mar = (MyAsyncResult) ar;
        mar.AsyncWaitHandle.WaitOne();
        return mar.Result; // will throw exception if one 
                           // occurred in background task
    }

    static void Main(string[] args)
    {
        // demo calling code
        // we don't need state or callback for demo
        IAsyncResult ar = Begin(42, null, null); 
        int result = End(ar);
        Console.WriteLine(result);
        Console.ReadLine();
    }
}

Для корректности важно, чтобы клиентский код не видел IAsyncResult реализация, иначе они могут получить доступ к таким методам, как SignalException некстати или прочитать Result преждевременно.Класс можно сделать более эффективным, не создавая WaitHandle выполнение (ManualResetEvent в примере), если в этом нет необходимости, но добиться 100% правильного результата сложно.Так же Thread и ManualResetEvent можно и нужно утилизировать End реализацию, как это следует делать со всеми объектами, которые реализуют IDisposable.И очевидно, End следует проверить, чтобы убедиться, что он получил реализацию правильного класса, чтобы получить более приятное исключение, чем исключение приведения.Я опустил эти и другие детали, поскольку они скрывают основную механику асинхронной реализации.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top