2010-08-10 3 views
4

나를 위해 일부 ID (예 : GUID)를 생성해야하는 클래스가 있다고 가정 해 보겠습니다. 불행히도 신분증 생성은 다소 긴 과정이며 만약 내가 100 명이 필요하다면 상당한 속도 저하의 문제가 발생합니다. 이를 방지하기 위해 미리 생성 된 ID 큐를 유지하고이 큐가 실행되기 시작하면 BackgroundWorker를 사용하여 새 큐를 생성하고 큐에 배치합니다. 하지만 내가 겪은 몇 가지 문제가 있습니다. 현재 가장 큰 방법은 대기열에 ID가 모두있는 경우 주 스레드가 BackroundWorker가 생성하여 대기열에 넣을 때까지 대기하는 방법입니다. 내가 가지고있는 코드를 보여줍니다.백그라운드 작업자 동기화

public class IdGenerator 
{ 
    private Queue<string> mIds = new Queue<string>(); 
    private BackgroundWorker mWorker = new BackgroundWorker(); 
    private static EventWaitHandle mWaitHandle = new AutoResetEvent(false); 

    public IdGenerator() 
    { 
     GenerateIds(); 

     this.mWorker.DoWork += new DoWorkEventHandler(FillQueueWithIds); 
    } 

    private void GenerateIds() 
    { 
     List<string> ids = new List<string>(); 

     for (int i = 0; i < 100; i++) 
     { 
      ids.Add(Guid.NewGuid().ToString()); 
     } 

     lock (this.mIds) 
     { 

      foreach (string id in ids) 
      { 
       this.mIds.Enqueue(id); 
      } 
     }    
    } 

    public string GetId() 
    { 
     string id = string.Empty; 

     lock (this.mIds) 
     { 
      if (this.mIds.Count > 0) 
      { 
       id = this.mIds.Dequeue(); 
      } 

      if (this.mIds.Count < 100) 
      { 
       if (!this.mWorker.IsBusy) 
       { 
        this.mWorker.RunWorkerAsync(); 
       } 
      } 
     } 

     if (this.mIds.Count < 1) 
     { 
      mWaitHandle.WaitOne(); 
     } 

     return id; 
    } 

    void FillQueueWithIds(object sender, DoWorkEventArgs e) 
    { 
     GenerateIds(); 
     mWaitHandle.Set(); 
    } 
} 

분명히 올바르게 작동하지 않습니다. 그것은 WaitOne 및 Set 메서드를 호출하는 적절한 타이밍 문제가있는 것 같습니다. 그리고 때로는 작업자가 이미 작업을 완료 했음에도 IsBusy 속성은 true를 반환합니다.


편집 :

그것의 WinForm과 내가 .NET 2.0

+0

이것은 실물의 실물 크기 인 것처럼 보입니다.이 코드에서도 오류가 발생합니까? 그것은 WinForms 또는 WPF 또는 ...인가요? –

+1

그것은 WinForm입니다. 위의 코드는 제거 된 버전이지만 완전히 똑같은 방식으로 작동하고 동일한 오류가 발생합니다. –

답변

3

당신이 가진 문제는 고전적인 생산자 - 소비자 문제는 사용하는 데 필요한거야. 살펴보기 http://en.wikipedia.org/wiki/Producer-consumer_problem

간단한 설명은 두 개의 스레드가 있다는 것입니다. 하나는 제작자 (GUID 생성기)이고 다른 하나는 소비자가됩니다.

세마포어를 사용하여 이러한 스레드를 동기화 상태로 유지합니다. 세마포어는 큐가 꽉 찼을 때 생산자를 멈추고 비어있을 때 소비자를 멈추게 할 책임이 있습니다.

위키 피 디아 문서에서 프로세스가 모두 잘 설명되어 있으며 인터넷에서 C#으로 Producer-Consumer의 기본 구현을 찾을 수 있습니다.

+0

팁 주셔서 감사. 나는 그것을 들여다 볼 것이다. –

1

주 코드 (아마도 WinForms)는 특정 시점에서 mWaitHandle.WaitOne()을 호출합니다. 그 순간 Messagepump가 차단되고 Bgw는 Completed 이벤트를 호출 할 수 없습니다. 즉, IsBusy 플래그가 true 인 경우 교착 상태가 발생합니다.

DoWork 내부의 코드가 예외를 throw하면 비슷한 문제가 발생할 수 있습니다.

편집 :

난 당신이 BGW을 대체 할 ThreadPool이 스레드를 사용하여 대부분의 문제를 해결할 수 있다고 생각합니다. 그리고 간단한 volatile bool isbusy 플래그.

+0

작업자가 상태를 변경하려면 주 스레드가 유휴 상태 여야합니다. 그냥 의심스러운 ... –

2

스레드 동기화와 관련된 몇 가지 버그가 있습니다 (아래 변경된 코드 참조). 대기열에 잠금 동기화를 적용하면 모든 대기열 사용을 잠그는 데주의를 기울여야합니다. GetId 메소드가 없으면 새 ID를 검사하도록 GetId 메소드를 변경했습니다.

public class IdGenerator 
{ 
    private Queue<string> mIds = new Queue<string>(); 
    private BackgroundWorker mWorker = new BackgroundWorker(); 
    private static EventWaitHandle mWaitHandle = new AutoResetEvent(false); 

    public IdGenerator() 
    { 
     GenerateIds(); 

     this.mWorker.DoWork += new DoWorkEventHandler(FillQueueWithIds); 
    } 

    private void GenerateIds() 
    { 
     List<string> ids = new List<string>(); 

     for (int i = 0; i < 100; i++) 
     { 
      ids.Add(Guid.NewGuid().ToString()); 
     } 

     lock (this.mIds) 
     { 

      foreach (string id in ids) 
      { 
       this.mIds.Enqueue(id); 
      } 
     }    
    } 

    public string GetId() 
    { 
     string id = string.Empty; 
     //Indicates if we need to wait 
     bool needWait = false; 

     do 
     { 
      lock (this.mIds) 
      { 
       if (this.mIds.Count > 0) 
       { 
        id = this.mIds.Dequeue(); 
        return id; 
       } 

       if (this.mIds.Count < 100 && this.mIds.Count > 0) 
       { 
        if (!this.mWorker.IsBusy) 
        { 
         this.mWorker.RunWorkerAsync(); 
        } 
       } 
       else 
       { 
        needWait = true; 
       } 
      } 

      if (needWait) 
      { 
       mWaitHandle.WaitOne(); 
       needWait = false; 
      } 
     } while(true); 

     return id; 
    } 

    void FillQueueWithIds(object sender, DoWorkEventArgs e) 
    { 
     GenerateIds(); 
     mWaitHandle.Set(); 
    } 
} 
+0

좋아, 고맙다. 그것은 여전히 ​​작동하지 않는다고 생각 ... –

3

.NET 4에서 당신은 BlockingCollection<T>을 사용할 수 있습니다 더 일반적으로 IProducerConsumerCollection<T>

는 여기를 사용하여이 작업을 하나 추가하고 다른 복용의 예입니다.

http://msdn.microsoft.com/en-us/library/dd997306.aspx

+0

내가 사용할 수있는 것 같은 소리 ... .NET 2.0에 충실 할 필요가 없다면. –

0

OK, 내가 갔다 heres는 최종 솔루션입니다. 이 하나는 BackgroundWorker를 사용하지 않지만 작동합니다.프로듀서 - 소비자 문제를 지적한 Edu에게 감사드립니다. 나는 here에 위치한 MSDN에서 제공하는 예제를 사용했다.

관련 문제