2011-07-12 110 views
1

非同期関数を呼び出す同じスレッドでコールバックメソッドを実行する方法。 呼び出し元のスレッドがUIスレッドではないかもしれない...しかし、UIは&よろしく、 ディネッシュスレッド切り替え

答えて

0

あるスレッドが別のスレッドにデリゲートの実行を開始させる魔法の弾丸はありません。ターゲットスレッドは、これを可能にするために特別に構成されなければなりません。 UIスレッドの場合、メッセージをディスパッチして処理するメッセージポンプがあります。このメッセージポンプを使用して、ISynchronizeInvokeインターフェイス経由でマーシャリング操作を実行できます。あなたのケースでは

ISynchronizeInvoke target = someForm; // where someForm is a Form or Control 
target.Invoke(
    (Action)(() => 
    { 
    MessageBox.Show("I am on the target thread"); 
    }), null); 

ワーカースレッドからそうするように指示された後、非同期関数を呼び出したスレッドは、そのスレッドで非同期的に実行するコールバックを得るためにそれに組み込まれた生産者 - 消費者機構のいくつかの種類を持っている必要があります。残念ながら、これは解決すべき小さな問題ではありません。

デリゲートを実行できるスレッドを作成する方法の1つです。

public class SynchronizeInvokeThread : ISynchronizeInvoke 
{ 
    private Thread m_Thread; 
    private BlockingCollection<WorkItem> m_Collection = new BlockingCollection<WorkItem>(); 

    public SynchronizeInvokeThread() 
    { 
     m_Thread = new Thread(
      () => 
      { 
       SynchronizationContext.SetSynchronizationContext(new MySynchronizationContext(this)); 
       while (true) 
       { 
        WorkItem wi = m_Collection.Take(); 
        wi.Complete(wi.Method.DynamicInvoke(wi.Args)); 
       } 
      }); 
     m_Thread.Start(); 
    } 

    public IAsyncResult BeginInvoke(Delegate method, object[] args) 
    { 
     var wi = new WorkItem(method, args); 
     m_Collection.Add(wi); 
     return wi; 
    } 

    public object EndInvoke(IAsyncResult result) 
    { 
     var wi = (WorkItem)result; 
     wi.AsyncWaitHandle.WaitOne(); 
     return wi.Result; 
    } 

    public object Invoke(Delegate method, object[] args) 
    { 
     var wi = new WorkItem(method, args); 
     m_Collection.Add(wi); 
     wi.AsyncWaitHandle.WaitOne(); 
     return wi.Result; 
    } 

    public bool InvokeRequired 
    { 
     get { return Thread.CurrentThread != m_Thread; } 
    } 

    private class MySynchronizationContext : SynchronizationContext 
    { 
     private ISynchronizeInvoke m_SynchronizingObject; 

     public MySynchronizationContext(ISynchronizeInvoke synchronizingObject) 
     { 
      m_SynchronizingObject = synchronizingObject; 
     } 

     public override void Post(SendOrPostCallback d, object state) 
     { 
      m_SynchronizingObject.BeginInvoke(d, new object[] { state }); 
     } 

     public override void Send(SendOrPostCallback d, object state) 
     { 
      m_SynchronizingObject.Invoke(d, new object[] { state }); 
     } 
    } 

    private class WorkItem : IAsyncResult 
    { 
     private Delegate m_Method; 
     private object[] m_Args; 
     private object m_Result = null; 
     private ManualResetEvent m_Signal = new ManualResetEvent(false); 

     public WorkItem(Delegate method, object[] args) 
     { 
      m_Method = method; 
      m_Args = args; 
     } 

     public void Complete(object result) 
     { 
      m_Result = result; 
      m_Signal.Set(); 
     } 

     public object Result 
     { 
      get { return m_Result; } 
     } 

     public Delegate Method 
     { 
      get { return m_Method; } 
     } 

     public object[] Args 
     { 
      get { return m_Args; } 
     } 

     public object AsyncState 
     { 
      get { return null; } 
     } 

     public WaitHandle AsyncWaitHandle 
     { 
      get { return m_Signal; } 
     } 

     public bool CompletedSynchronously 
     { 
      get { return false; } 
     } 

     public bool IsCompleted 
     { 
      get { return m_Signal.WaitOne(0); } 
     } 
    } 
} 

このように使用できます。

ISynchronizeInvoke target = new SynchronizeInvokeThread(); 
target.Invoke(
    (Action)(() => 
    { 
    Console.WriteLine("I am on the target thread"); 
    SynchronizationContext.Current.Post(
     (state) => 
     { 
     Console.WriteLine("I even have a synchronization context!"); 
     }, null); 
    }), null); 

更新:BlockingCollection以下のコメントパー

.NET 4.0またはReactive Extensionsダウンロードの一部としてのみ使用可能です。このデータ構造が利用できない場合は、すでに難しいコードがさらに難しくなりました。

+0

は、任意のサンプルはありますか? – dinesh

+0

答えを更新しました。 –

+0

@Brain Gideon: 'CustomThread'を使用すると、ターゲットスレッドでコードが呼び出されません。あなたが 'SynchronizationContext'を使ってそれを証明していないからです。 Windowsフォームでテストを実行し、 'Console.WriteLine'を' textBox1.Text = "some data"; "に置き換えてください。クロススレッド例外が発生します。また、質問タグが 'C#3.0'のところに' ConcurrentCollection'を使用していることにも注意してください。 –

0

使用BackgroundWorkerのを..ハング

感謝すべきではありません。コールバックは所有スレッド上にあります。

コールバック後でも非同期操作を実行する必要がある場合は、WPF System.Windows.Application.Current.Dispatcher.Invoke/BeginInvokeを使用して複数のコールバックを行うことができます。WinFormsの場合は、フォームまたはコントロールインスタンス自体を呼び出して、Invoke/BeginInvokeを呼び出します。

+0

それは、その後のBackgroundWorkerのCompletedイベントが別のスレッドで呼び出されたコンソール・アプリケーションである場合.. .. – dinesh

0

Brian Gideonのように、ISynchronizeInvoke "System.ComponentModel.ISynchronizeInvoke"を使用する必要があります。別のスレッドでスレッドの実行をマーシャリングするクラスで実装します。ここでの例Mediaクラス "私が実装したクラスは、Comオブジェクトとやりとりしていたので、メインスレッドでそのメソッドを実行すべきです";クラス実装のためにSystem.Threading.SynchronizationContext.Currentを使用するので、System.Threading.SynchronizationContext.Currentがnullであるため、WindowsFormsでは使用できますがコンソールアプリケーションでは使用できません。

このクラスの実行を、それを作成したスレッドにマーシャリングしたいときはいつでも、そのメソッドのInvokeを呼び出します。我々はRunAsyncのfnctionを呼び出しているでは

public abstract class Media : ISynchronizeInvoke 
{ 
     //.... 

     private readonly System.Threading.SynchronizationContext _currentContext = System.Threading.SynchronizationContext.Current; 

     private readonly System.Threading.Thread _mainThread = System.Threading.Thread.CurrentThread; 

     private readonly object _invokeLocker = new object(); 
     //.... 


     #region ISynchronizeInvoke Members 

     public bool InvokeRequired 
     { 
      get 
      { 
       return System.Threading.Thread.CurrentThread.ManagedThreadId != this._mainThread.ManagedThreadId; 
      } 
     } 

     /// <summary> 
     /// This method is not supported! 
     /// </summary> 
     /// <param name="method"></param> 
     /// <param name="args"></param> 
     /// <returns></returns> 
     [Obsolete("This method is not supported!", true)] 
     public IAsyncResult BeginInvoke(Delegate method, object[] args) 
     { 
      throw new NotSupportedException("The method or operation is not implemented."); 
     } 

     /// <summary> 
     /// This method is not supported! 
     /// </summary> 
     /// <param name="method"></param> 
     /// <param name="args"></param> 
     /// <returns></returns> 
     [Obsolete("This method is not supported!", true)] 
     public object EndInvoke(IAsyncResult result) 
     { 
      throw new NotSupportedException("The method or operation is not implemented."); 
     } 

     public object Invoke(Delegate method, object[] args) 
     { 
      if (method == null) 
      { 
       throw new ArgumentNullException("method"); 
      } 

      lock (_invokeLocker) 
      { 
       object objectToGet = null; 

       SendOrPostCallback invoker = new SendOrPostCallback(
       delegate(object data) 
       { 
        objectToGet = method.DynamicInvoke(args); 
       }); 

       _currentContext.Send(new SendOrPostCallback(invoker), method.Target); 

       return objectToGet; 
      } 
     } 

     public object Invoke(Delegate method) 
     { 
      return Invoke(method, null); 
     } 

     #endregion//ISynchronizeInvoke Members 

} 
関連する問題