2011-12-18 4 views
2

내 응용 프로그램에서 스레딩 문제가 있습니다. 다중 스레드 클라이언트/서버 응용 프로그램이 있습니다. 또한 Unity3d에 C# MonoDevelop를 사용하고 있습니다. 대답에 차이가 있는지 확실하지 않습니다. 내 문제가 어디 있는지 설명하려고합니다 :C# 다른 스레드에서 코드 실행

단일 스레드에서 작동합니다. 따라서 추상적 클래스 인 ScriptableObject을 사용하는 객체를 인스턴스화하려면 Unity가 실행되는 주 스레드에서 수행해야합니다.

그러나 내 서버 소켓은 연결된 모든 클라이언트에 대해 스레드를 생성하므로 들어오는 데이터를 비동기 적으로 처리 할 수 ​​있습니다. 수신 된 데이터는 (자신의 실행 스레드에서)에있어서 OnDataReceived()

여기서 문제가 처리되고 난 OnDataReceived() 실 내부 Player 오브젝트의 인스턴스를 생성 할 수 있다는 것이다. 내 Player 개체가 ScriptableObject에서 상속되기 때문입니다. 즉,이 객체가 주 Unity 스레드에서 만들어 져야 함을 의미합니다.

그러나 어떻게 해야할지 모르겠다 ... 메인 스레드로 다시 전환 할 수있는 방법이 있습니까? OnDataReceived() 메서드에서 개체를 만들 수 있습니까? 당신은

class Communicator 
{ 
    public static volatile bool CreatePlayer; 
} 

으로 그리고 소켓 코드에서 클래스를 통해 원래의 스레드와 통신 할 수

+0

당신이 Ubity3d가 다른 스레드에서 만든 개체를 사용 못하게 확실합니까? –

+0

@HenkHolterman 100 % 확신 할 수는 없지만 그 것처럼 보입니다. 다른 메소드/클래스에서 Player를 인스턴스화하면 모든 것이 잘 동작합니다. 하지만 내 소켓 클래스 (다른 스레드에서 실행되는)에서 그것을하려고하면 오류가 발생합니다 -> 오류 : 파일 : MonoManager.cpp at line : 2212 --- 그게 전부입니다. 그래서 이것은 다른 스레드에서만 발생하기 때문에 문제가 될 것이라고 생각했습니다. – w00

+0

Unity3D를 사용하면 스레드간에 변수를 사용할 수 있습니다. 경주 조건처럼 들립니다. – Kay

답변

4

.NET은 이미 UI 컨트롤 (예 : WPF 또는 WinForms)에서 작업을 호출하기 위해 스레드 선호도가 필요한 UI 응용 프로그램에 가장 자주 사용되는 SynchronizationContext이라는 개념을 가지고 있습니다. 그러나 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 변수를 변경합니다. 수신 코드에서 변수를 확인하고 플레이어를 만듭니다. 그런 다음 CreatePlayer를 false로 설정하십시오. 다른 것들과 비슷하게. 동시에 두 개의 스레드에서 하나의 변수를 조작하는 것에주의하십시오. 예를 들어 두 스레드가 지속적으로 동일한 데이터에 액세스하지 못하도록 NumPlayersToCreate int를 갖는 것보다 CreatePlayer에 대해 네 개의 부울을 갖는 것이 더 좋습니다. 물론 프로필을 작성하고보아야합니다. 마지막으로 두 가지 스레드에서 변경된 변수가 휘발성으로 표시되는지 확인하십시오. 이렇게하면 각 스레드가 캐시에 보관하는 대신 주 메모리에서 데이터에 액세스하게됩니다. 그렇지 않으면 각 스레드가 다른 스레드의 캐시에서 변경되는 데이터를 알지 못합니다.

예, 이것은 가장 성능이 뛰어나고 우아한 해결책은 아니지만 가장 간단합니다. 누군가가 더 많은 것을 제안 할 것이라고 확신합니다. 네가 원한다면 나는 그걸 할 수있어. 그러나 멀티 스레딩에 익숙하지 않은 것처럼 보였으므로 뭔가 간단하게 시작하고 싶다고 생각했습니다.

+0

그래서 기본적으로 CreatePlayer를 'true'로 설정해야한다는 메시지를 내 소켓 메서드 인 OnDataReceived()에서받을 수 있습니까? 그러면 CreatePlayer가 true로 설정되어 있는지 확인하기 위해 Update() 루프 (모든 프레임 렌더라고 함)와 같은 것이 필요하다는 것을 의미합니다. 그렇다면 거기에 플레이어를 만듭니다. 그게 맞습니까? – w00

+0

예. 그것은 보이는 것처럼 간단합니다. : P – GGulati

관련 문제