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

Мои знания о многопоточности все еще довольно элементарны, поэтому я очень ценю некоторые советы здесь. У меня есть интерфейс 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 своим собственным, чтобы я мог вернуться к теме, которую мы надували... но, честно говоря, я немного не в себе. Есть указатели?

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

Джеймс

1 ответ

Решение

Самый простой способ асинхронно реализовать синхронный метод - это поместить его в делегат и использовать 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 следует проверить, что он получил реализацию правильного класса, чтобы получить более приятное исключение, чем исключение приведения. Я пропустил эти и другие детали, поскольку они затеняют основную механику асинхронной реализации.

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