Pregunta

Mi conocimiento multi-threading es todavía bastante rudimentaria, por lo que realmente apreciaría algunos consejos aquí. Tengo una interfaz, IOperationInvoker (de WCF) que tiene los siguientes métodos:

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

Dada una aplicación concreta de esta interfaz, lo que necesito para implementar la misma interfaz, mientras que llamar a la aplicación subyacente en un hilo separado. (En caso de que se esté preguntando por qué, la implmentation concreto llama a un objeto COM legado que debe estar en un estado apartamento diferente).

Por el momento, estoy haciendo algo como esto:

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

Estoy pensando que necesito para concluir la AsyncResult de volver con mi propia, para que pueda volver a la rosca que hemos entrado en la cola ... pero la verdad es que estoy un poco fuera de mi profundidad. Cualquier punteros?

Muchas gracias,

James

¿Fue útil?

Solución

La forma más fácil de poner en práctica un método sincrónico de forma asíncrona es para ponerlo en un delegado, y el uso de los métodos y BeginInvoke EndInvoke en el delegado resultante. Esto ejecutará el método sincrónico en un hilo de subprocesos y BeginInvoke devolverá una aplicación IAsyncResult, por lo que no tiene que poner en práctica las entrañas de la misma. Sin embargo, sí es necesario para pasar de contrabando un poco de datos adicionales en el IAsyncResult devuelto por IOperationInvoker.InvokeEnd. Usted podría hacerlo fácilmente mediante la creación de una implementación de IAsyncResult que los delegados de todo a un IAsyncResult interior, pero tiene un campo adicional para contener el delegado, para que cuando la instancia IAsyncResult se pasa a InvokeEnd, puede acceder a la delegada para llamar EndInvoke en él .

Sin embargo, después de la lectura más cerca de su pregunta, veo que es necesario utilizar un hilo explícita con la configuración de COM, etc.

Lo que hay que hacer es poner en práctica adecuadamente IAsyncResult. Casi todo sigue de esto, ya que el IAsyncResult contendrá todos los bits necesarios para la sincronización.

Aquí hay una muy simple, pero no es terriblemente eficiente, la implementación de IAsyncResult. Se encapsula todas las características esenciales:. Argumentos que pasan, un evento de sincronización, aplicación de devolución de llamada, las excepciones que se propagan desde la tarea asíncrona y el resultado de volver

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

Es importante para la corrección de que el código del cliente no puede ver la aplicación IAsyncResult, de lo contrario podría tener acceso a métodos como SignalException inapropiada o leer Result prematuramente. La clase se puede hacer más eficiente al no construir la aplicación WaitHandle (ManualResetEvent en el ejemplo) si no es necesario, pero esto es difícil de obtener el 100% correcto. Además, el Thread y ManualResetEvent pueden y deben ser eliminados de la implementación End, como debería hacerse con todos los objetos que implementan IDisposable. Y, obviamente, End debe comprobar para asegurarse de que se ha conseguido una implementación de la clase adecuada para obtener una excepción más agradable que una excepción de difusión. He dejado estos y otros detalles como oscurecen la mecánica esenciales de la implementación asíncrona.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top