2011-12-18 7 views
2

私のアプリケーションでスレッド化に問題があります。私はマルチスレッドクライアント/サーバーアプリケーションを持っています。 Unity3dにはC#MonoDevelopも使用しています。答えに違いがあるかどうかは不明です。私は自分の問題がどこにあるのか説明しようとします:C#他のスレッドでコードを実行

Unityは単一のスレッドで動作します。だから抽象クラスScriptableObjectを使用してオブジェクトをインスタンス化したい場合は、これをUnityが動作するメインスレッドで行う必要があります。

私のサーバーソケットは接続されているすべてのクライアントのスレッドを生成します。そのため、受信データを非同期に処理できます。受信データが(それ自身のスレッド上で実行される)OnDataReceived()方法

で処理され、ここでの問題は、私はOnDataReceived()スレッド内部Playerオブジェクトのインスタンスを作成することができないということです。私のPlayerオブジェクトはScriptableObjectから継承されています。これは、このオブジェクトがメインのUnityスレッド上に作成されるべきであることを意味します。

しかし、私はそれを行う方法がわかりません...メインスレッドに戻る方法はありますか?私はまだオブジェクトをOnDataReceived()メソッドで作成できますか?

+0

あなたはUbity3dは、あなたが別のスレッドで作成されたオブジェクトを使用させませんか? –

+0

@HenkHolterman私は100%確信していませんが、それはそれのように見えます。他のメソッド/クラスでPlayerをインスタンス化すると、すべて正常に動作します。しかし、私は私のソケットクラス(別のスレッドで実行されている)からそれをしようとすると、エラーをスローする - >ファイルのエラー:MonoManager。cpp at line:2212 ---それはすべて私が得る。だから、これは別のスレッドでしか起こらないので、それが問題だと私は思った。 – w00

+0

Unity3Dでは、スレッド間で変数を使用できます。競争状態のように聞こえる。 – Kay

答えて

4

.NETにはすでにSynchronizationContextの概念があります。これは、UIコントロール(たとえばWPFやWinFormsなど)で操作を呼び出すためにスレッドアフィニティが必要なUIアプリケーションによく使用されます。しかし、UIアプリケーションの外部であっても、これらの概念を汎用目的のスレッドアフィニティ化された作業キューに再利用することができます。

このサンプルでは、​​バックメインプログラムスレッドの子スレッドに起因するアクションを呼び出すために一緒に.NET 4.0タスククラス(TaskScheduler/Task)で、シンプルなコンソールアプリケーションで(WindowsBase.dllから)WPF DispatcherSynchronizationContextを使用する方法を示しています。

using System; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows.Threading; 

internal sealed class Program 
{ 
    private static void Main(string[] args) 
    { 
     int threadCount = 2; 
     using (ThreadData data = new ThreadData(threadCount)) 
     { 
      Thread[] threads = new Thread[threadCount]; 
      for (int i = 0; i < threadCount; ++i) 
      { 
       threads[i] = new Thread(DoOperations); 
      } 

      foreach (Thread thread in threads) 
      { 
       thread.Start(data); 
      } 

      Console.WriteLine("Starting..."); 

      // Start and wait here while all work is dispatched. 
      data.RunDispatcher(); 
     } 

     // Dispatcher has exited. 
     Console.WriteLine("Shutdown."); 
    } 

    private static void DoOperations(object objData) 
    { 
     ThreadData data = (ThreadData)objData; 
     try 
     { 
      // Start scheduling operations from child thread. 
      for (int i = 0; i < 5; ++i) 
      { 
       int t = Thread.CurrentThread.ManagedThreadId; 
       int n = i; 
       data.ExecuteTask(() => SayHello(t, n)); 
      } 
     } 
     finally 
     { 
      // Child thread is done. 
      data.OnThreadCompleted(); 
     } 
    } 

    private static void SayHello(int requestingThreadId, int operationNumber) 
    { 
     Console.WriteLine(
      "Saying hello from thread {0} ({1}) on thread {2}.", 
      requestingThreadId, 
      operationNumber, 
      Thread.CurrentThread.ManagedThreadId); 
    } 

    private sealed class ThreadData : IDisposable 
    { 
     private readonly Dispatcher dispatcher; 
     private readonly TaskScheduler scheduler; 
     private readonly TaskFactory factory; 
     private readonly CountdownEvent countdownEvent; 

     // In this example, we initialize the countdown event with the total number 
     // of child threads so that we know when all threads are finished scheduling 
     // work. 
     public ThreadData(int threadCount) 
     { 
      this.dispatcher = Dispatcher.CurrentDispatcher; 
      SynchronizationContext context = 
       new DispatcherSynchronizationContext(this.dispatcher); 
      SynchronizationContext.SetSynchronizationContext(context); 
      this.scheduler = TaskScheduler.FromCurrentSynchronizationContext(); 
      this.factory = new TaskFactory(this.scheduler); 
      this.countdownEvent = new CountdownEvent(threadCount); 
     } 

     // This method should be called by a child thread when it wants to invoke 
     // an operation back on the main dispatcher thread. This will block until 
     // the method is done executing. 
     public void ExecuteTask(Action action) 
     { 
      Task task = this.factory.StartNew(action); 
      task.Wait(); 
     } 

     // This method should be called by threads when they are done 
     // scheduling work. 
     public void OnThreadCompleted() 
     { 
      bool allThreadsFinished = this.countdownEvent.Signal(); 
      if (allThreadsFinished) 
      { 
       this.dispatcher.InvokeShutdown(); 
      } 
     } 

     // This method should be called by the main thread so that it will begin 
     // processing the work scheduled by child threads. It will return when 
     // the dispatcher is shutdown. 
     public void RunDispatcher() 
     { 
      Dispatcher.Run(); 
     } 

     public void Dispose() 
     { 
      this.Dispose(true); 
      GC.SuppressFinalize(this); 
     } 

     // Dispose all IDisposable resources. 
     private void Dispose(bool disposing) 
     { 
      if (disposing) 
      { 
       this.countdownEvent.Dispose(); 
      } 
     } 
    } 
} 

出力例:

 
Starting... 
Saying hello from thread 3 (0) on thread 1. 
Saying hello from thread 4 (0) on thread 1. 
Saying hello from thread 3 (1) on thread 1. 
Saying hello from thread 4 (1) on thread 1. 
Saying hello from thread 3 (2) on thread 1. 
Saying hello from thread 4 (2) on thread 1. 
Saying hello from thread 3 (3) on thread 1. 
Saying hello from thread 4 (3) on thread 1. 
Saying hello from thread 3 (4) on thread 1. 
Saying hello from thread 4 (4) on thread 1. 
Shutdown. 
+2

+1非常に洗練されたanwser – Kay

2

あなたはCreatePlayer変数を変更する、など

class Communicator 
{ 
    public static volatile bool CreatePlayer; 
} 

とソケットコードのようにクラスを介して元のスレッドと通信することができます。レシーバコードで、変数をチェックしてプレーヤを作成します。その後、CreatePlayerをfalseに設定します。他のものと同様に同時に2つのスレッド間で1つの変数を操作することに注意してください。たとえば、NumPlayersToCreateを持つよりもCreatePlayerに4つのブール値を持つ方が、両方のスレッドが常に同じデータにアクセスしようとしない方がよい場合があります。もちろん、プロファイルを作成して表示する必要があります。最後の1つのこと:両方のスレッドで変更された変数がvolatileとしてマークされていることを確認してください。これにより、各スレッドは、キャッシュに保持するのではなく、主メモリからデータにアクセスします(そうしないと、各スレッドは、他のスレッドのキャッシュで変更されているデータに気付かないでしょう)。

はい、これは最も機能的で洗練された解決策ではありませんが、最も簡単です。誰かがもっと関わるものを提案すると確信しています。もしあなたが望めば、私もそれをすることができます。しかし、あなたはマルチスレッドに慣れていないようですので、何かを簡単に始めたいと思っていました。

+0

これは、私のソケットメソッドOnDataReceived()でそのメッセージを受け取ったときにCreatePlayerを 'true'に設定する必要があるということです。次に、CreatePlayerがtrueに設定されているかどうかを確認するUpdate()ループ(すべてのフレームレンダリングと呼ばれる)のようなものが必要であることを意味し、そうであればそこにPlayerを作成します。そうですか? – w00

+0

はい。それはまるで同じように簡単です。 :P – GGulati

関連する問題