Асинхронные операции внутри асинхронной операции - PullRequest
2 голосов
/ 18 апреля 2009

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

1 голос
/ 18 апреля 2009

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

...