2017-11-21 14 views
-1

メインスレッドでスレッドセーフではないクラスXがあり、別のスレッドに別のクラスYがあり、メソッドdoX()を呼び出す必要があるとします。クラスXの参照をクラスYに渡し、YからdoX()を呼び出すだけですが、このクラスXはスレッドセーフではなく、別のスレッドから呼び出された場合は奇妙に動作します。別のスレッドからスレッドで非スレッドセーフなメソッドを呼び出す

XのスレッドからXのメソッドdoX()をYに呼び出させるにはどうすればよいですか? managedthreadid以下のSSCCでは、常に同じである必要があります(そうではありません)。

using System; 
using System.Threading; 

namespace ThreadApp 
{ 
    static class Program 
    { 
     [STAThread] 
     static void Main() 
     { 
      int managedThreadId = Thread.CurrentThread.ManagedThreadId; 
      System.Diagnostics.Debug.WriteLine("Main ManagedThreadId = " + managedThreadId); 

      X x = new X(); 
      x.doX(); 

      Y y = new Y(); 
      y.fun(x); 
     } 
    } 

    class X 
    { 
     public void doX() 
     { 
      int managedThreadId = Thread.CurrentThread.ManagedThreadId; 
      System.Diagnostics.Debug.WriteLine("X ManagedThreadId = " + managedThreadId); 
     } 
    } 

    class Y 
    { 
     public void fun(X x) 
     { 
      Thread t = new Thread(x.doX); 
      t.Start(); 
     } 
    } 
} 

EDIT:このページでは、私ができるより良い私の問題について説明します。http://mikehadlow.blogspot.it/2012/11/using-blockingcollection-to-communicate.html

は、これらの(やや)一般的なプログラミングの課題を考えてみましょう:

私はそのサードパーティのライブラリを使用していますスレッドセーフではありませんが、 アプリケーションで複数のスレッド間で作業を共有したいと考えています。 私のマルチスレッドコードとシングルスレッドライブラリの間で呼び出しをどのようにマーシャリングするのですか?私は 単一のスレッドでイベントの単一のソースを持っていますが、 複数のスレッドのプール間で仕事を共有したいですか?私は複数のスレッドを持っている イベントが、私は単一のスレッドでそれらを消費したいですか? 1つの これを行う方法は、いくつかの共有状態、フィールドまたは静的クラスの プロパティを持ち、複数の スレッドが安全にアクセスできるように、その周りにラップをロックすることです。これはかなり普通の方法です この特定の猫を皮膚に襲い掛けますが、それは不注意のため のためのトラップで撃たれます。また、共有する リソースへのアクセスは、アクセスするものが であるにもかかわらず、シリアル化されているため、パフォーマンスが低下する可能性があります。

BlockingCollectionを使用してスレッド をメッセージクラス経由で通信させることをお勧めします。

ここBlockingCollectionを使用してのWebサイトの提案に基づき、ワーキングソリューションです:

namespace ThreadApp 
{ 
    static class Program 
    { 
     [STAThread] 
     static void Main() 
     { 
      int managedThreadId = Thread.CurrentThread.ManagedThreadId; 
      System.Diagnostics.Debug.WriteLine("Main ManagedThreadId = " + managedThreadId); 

      X x = new X(); 

      Y y = new Y(); 
      y.fun(x); 

      x.doX(); 

     } 
    } 

    class X 
    { 
     private BlockingCollection<String> queue = new BlockingCollection<String>(); 

     public void Produce(String item) 
     { 
      queue.Add(item); 
     } 

     public void doX() 
     { 
      while (true) 
      { 
       String item = queue.Take(); 
       int managedThreadId = Thread.CurrentThread.ManagedThreadId; 
       System.Diagnostics.Debug.WriteLine("X ManagedThreadId = " + managedThreadId + " randomid=" + item); 
       // Add your code to process the item here. 
       // Do not start another task or thread. 
      } 
     } 
    } 

    class Y 
    { 
     X x; 

     public void fun(X x) 
     { 
      this.x = x; 
      Thread t = new Thread(threadBody); 
      t.Start(); 
     } 

     void threadBody() 
     { 
      while (true) 
      { 
       int managedThreadId = Thread.CurrentThread.ManagedThreadId; 

       Random rand = new Random(); 
       int randInt = rand.Next(1, 90); 
       System.Diagnostics.Debug.WriteLine("Y ManagedThreadId = " + managedThreadId + " random-int" + randInt); 
       x.Produce("random-int" + randInt); 
       Thread.Sleep(randInt * 10); 
      } 
     } 
    } 
} 

上記の解決策は動作しますが、ここでの出力があります:

Main ManagedThreadId = 1 
Y ManagedThreadId = 3 random-int24 
X ManagedThreadId = 1 randomid=random-int24 
Y ManagedThreadId = 3 random-int46 
X ManagedThreadId = 1 randomid=random-int46 
Y ManagedThreadId = 3 random-int48 
X ManagedThreadId = 1 randomid=random-int48 

Yスレッドがランダム-intを挿入Xスレッドはキュー内でそれを受け取り、そのメソッドをMainスレッドと同じスレッドで実行します。

しかし、問題はdoX()メソッドがwhileループ内にあり、ブロックされていることです。他のいくつかの関数を持つXクラスがあり、メソッド内でループをブロックできない場合、このアプローチはうまくいかないでしょう...

+1

スレッドを保持する必要がありますか、2つのスレッドがメソッドを同時に呼び出さないようにしたいですか? –

+0

上記のSSCCでは、2つのスレッドがdoX()メソッドを呼び出していても、managedThreadIdは1つだけ出力されます。私はYがdoX()メソッドをXメインスレッドから呼び出されたかのように実行させたい。メソッドの呼び出しを同期させることはポイントではありません –

+0

2番目のスレッドは、最初のスレッドでコードを実行しません。ただし、最初のスレッドでコードを実行できるように、最初のスレッドに通知することができます。 Windowsアプリケーションでは、メッセージで通知します。このようなアプリケーションでは、EventWaitHandleまたはそのサブクラスのAutoResetEventまたはManualResetEventを使用します。最初のスレッドは、通知されるイベントを待ちます。 2番目のスレッドはイベントをSet()して、最初のスレッドに何らかのコードを実行するよう通知します。 2番目のスレッドにイベントを渡し、Setを呼び出すことができます。 event.WaitOne()を使用して、最初のスレッドのシグナルを待ちます。サンプルコードが必要な場合はお知らせください。 – Lorek

答えて

2

これは素晴らしいアプローチです。 Microsoftのリアクティブフレームワーク(Rx)を使用します。

Rxは主に、非常に強力な観測可能/オブザーバモデルを提供しますが、スレッドで簡単に作業できるスケジューラも用意されています。 EventLoopSchedulerスケジューラを使用して、コードが1つのスレッド上で確実に実行されるようにすることができます。

は、この例を試してみてください。

var els = new System.Reactive.Concurrency.EventLoopScheduler(); 

Console.WriteLine("A" + Thread.CurrentThread.ManagedThreadId); 

els.Schedule(() => 
{ 
    Console.WriteLine("B" + Thread.CurrentThread.ManagedThreadId); 
}); 

var thread = new Thread((ThreadStart)(() => 
{ 
    Console.WriteLine("C" + Thread.CurrentThread.ManagedThreadId); 
    els.Schedule(() => 
    { 
     Console.WriteLine("D" + Thread.CurrentThread.ManagedThreadId); 
    }); 
})); 

thread.Start(); 

それは出力:

 
A12 
B14 
C16 
D14 

同じスレッド上の両方"B""D"実行アクションをスケジュールするための呼び出しは、2つの異なるスレッドから来たにも関わらず。

EventLoopSchedulerを使用すると、Xのコードを同じスレッドで実行することができます。

ちょうどNuGet "System.Reactive"でビットを取得します。

関連する問題