2009-04-18 3 views
2

Mes connaissances multi-threads sont encore assez rudimentaires, donc j'apprécierais vraiment quelques pointeurs ici. J'ai une interface, IOperationInvoker (de WCF) qui a les méthodes suivantes:Opérations asynchrones au sein d'une opération asynchrone

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

Compte tenu d'une mise en œuvre concrète de cette interface, je dois implémenter la même interface, tout en appelant la mise en œuvre sous-jacente dans une discussion séparée. (dans le cas où vous vous demandez pourquoi, l'implmentation concrète appelle un objet COM hérité qui doit être dans un état d'appartement différent).

En ce moment, je fais quelque chose comme ceci:

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

Je pense que je dois conclure le AsyncResult retourné avec mon propre, donc je peux revenir au fil que nous avons bobiné ... mais honnêtement, je suis un peu hors de ma profondeur. Des pointeurs?

Un grand merci,

James

Répondre

1

La meilleure façon de mettre en œuvre une méthode synchrone est de le mettre dans un mode asynchrone délégué, et d'utiliser les méthodes BeginInvoke et EndInvoke sur le délégué résultant. Cela exécutera la méthode synchrone sur un thread threadpool, et BeginInvoke renverra une implémentation IAsyncResult, donc vous n'avez pas à implémenter le courage de celui-ci. Cependant, vous avez besoin de faire passer un peu de données supplémentaires dans le IAsyncResult retourné par IOperationInvoker.InvokeEnd. Vous pouvez le faire facilement en créant une implémentation de IAsyncResult qui délègue tout à un IAsyncResult interne, mais qui contient un champ supplémentaire pour contenir le délégué, de sorte que lorsque l'instance IAsyncResult est transmise à InvokeEnd, vous pouvez accéder au délégué pour l'appeler EndInvoke .

Cependant, après lecture plus attentive de votre question, je vois que vous avez besoin d'utiliser un fil explicite avec les paramètres COM etc.

Ce que vous devez faire est de mettre en œuvre correctement IAsyncResult. Presque tout en découle, puisque le IAsyncResult contiendra tous les bits nécessaires à la synchronisation.

Voici une implémentation très simple, mais pas très efficace, de IAsyncResult. Il encapsule toutes les fonctionnalités essentielles: passer des arguments, un événement de synchronisation, une implémentation de rappel, propager des exceptions à partir d'une tâche asynchrone et renvoyer un résultat.

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

Il est important pour l'exactitude que le code client ne peut pas voir la mise en œuvre IAsyncResult, sinon ils pourraient accéder à des méthodes comme SignalException inappropriée ou lire Result prématurément. La classe peut être rendue plus efficace en ne construisant pas l'implémentation WaitHandle (ManualResetEvent dans l'exemple) si ce n'est pas nécessaire, mais c'est difficile à obtenir à 100%. En outre, les Thread et ManualResetEvent peuvent et doivent être éliminés dans l'implémentation End, comme cela devrait être fait avec tous les objets qui implémentent IDisposable. Et évidemment, End devrait vérifier pour s'assurer qu'il a obtenu une implémentation de la bonne classe pour obtenir une plus belle exception qu'une exception cast. J'ai laissé ces détails et d'autres à mesure qu'ils obscurcissent les mécanismes essentiels de la mise en œuvre asynchrone.

+0

Merci beaucoup Barry - Je vais essayer! –