2013-05-26 3 views
4

간단한 스레드 풀 만들기에 대해이 thread을 보았습니다. 그곳에서 나는 우아한이었다 @MilanGardian's response for .NET 3.5을 가로 질러 와서 내 목적을 역임 :스레드를 만들 때 NullReferenceException이 발생했습니다

static void Main(string[] args) 
{ 
    ... 
    ... 
     while(keepRunning) 
     { 
     ... 
     pool.QueueTask(() => DoTask(eventObject); 
     } 
    ... 
} 

private static void DoTask(EventObject e) 
{ 
    // Do some computations 

    pool.QueueTask(() => DoAnotherTask(eventObject)); // this is a relatively smaller computation 
} 

을 나는 약 2 일 동안 코드를 실행 한 후 다음과 같은 예외가 받고 다음과 같이 내가 이것을 사용하고

using System; 
using System.Collections.Generic; 
using System.Threading; 

namespace SimpleThreadPool 
{ 
    public sealed class Pool : IDisposable 
    { 
     public Pool(int size) 
     { 
      this._workers = new LinkedList<Thread>(); 
      for (var i = 0; i < size; ++i) 
      { 
       var worker = new Thread(this.Worker) { Name = string.Concat("Worker ", i) }; 
       worker.Start(); 
       this._workers.AddLast(worker); 
      } 
     } 

     public void Dispose() 
     { 
      var waitForThreads = false; 
      lock (this._tasks) 
      { 
       if (!this._disposed) 
       { 
        GC.SuppressFinalize(this); 

        this._disallowAdd = true; // wait for all tasks to finish processing while not allowing any more new tasks 
        while (this._tasks.Count > 0) 
        { 
         Monitor.Wait(this._tasks); 
        } 

        this._disposed = true; 
        Monitor.PulseAll(this._tasks); // wake all workers (none of them will be active at this point; disposed flag will cause then to finish so that we can join them) 
        waitForThreads = true; 
       } 
      } 
      if (waitForThreads) 
      { 
       foreach (var worker in this._workers) 
       { 
        worker.Join(); 
       } 
      } 
     } 

     public void QueueTask(Action task) 
     { 
      lock (this._tasks) 
      { 
       if (this._disallowAdd) { throw new InvalidOperationException("This Pool instance is in the process of being disposed, can't add anymore"); } 
       if (this._disposed) { throw new ObjectDisposedException("This Pool instance has already been disposed"); } 
       this._tasks.AddLast(task); 
       Monitor.PulseAll(this._tasks); // pulse because tasks count changed 
      } 
     } 

     private void Worker() 
     { 
      Action task = null; 
      while (true) // loop until threadpool is disposed 
      { 
       lock (this._tasks) // finding a task needs to be atomic 
       { 
        while (true) // wait for our turn in _workers queue and an available task 
        { 
         if (this._disposed) 
         { 
          return; 
         } 
         if (null != this._workers.First && object.ReferenceEquals(Thread.CurrentThread, this._workers.First.Value) && this._tasks.Count > 0) // we can only claim a task if its our turn (this worker thread is the first entry in _worker queue) and there is a task available 
         { 
          task = this._tasks.First.Value; 
          this._tasks.RemoveFirst(); 
          this._workers.RemoveFirst(); 
          Monitor.PulseAll(this._tasks); // pulse because current (First) worker changed (so that next available sleeping worker will pick up its task) 
          break; // we found a task to process, break out from the above 'while (true)' loop 
         } 
         Monitor.Wait(this._tasks); // go to sleep, either not our turn or no task to process 
        } 
       } 

       task(); // process the found task 
       this._workers.AddLast(Thread.CurrentThread); 
       task = null; 
      } 
     } 

     private readonly LinkedList<Thread> _workers; // queue of worker threads ready to process actions 
     private readonly LinkedList<Action> _tasks = new LinkedList<Action>(); // actions to be processed by worker threads 
     private bool _disallowAdd; // set to true when disposing queue but there are still tasks pending 
     private bool _disposed; // set to true when disposing queue and no more tasks are pending 
    } 


    public static class Program 
    { 
     static void Main() 
     { 
      using (var pool = new Pool(5)) 
      { 
       var random = new Random(); 
       Action<int> randomizer = (index => 
       { 
        Console.WriteLine("{0}: Working on index {1}", Thread.CurrentThread.Name, index); 
        Thread.Sleep(random.Next(20, 400)); 
        Console.WriteLine("{0}: Ending {1}", Thread.CurrentThread.Name, index); 
       }); 

       for (var i = 0; i < 40; ++i) 
       { 
        var i1 = i; 
        pool.QueueTask(() => randomizer(i1)); 
       } 
      } 
     } 
    } 
} 

를 :

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object. 
    at System.Collections.Generic.LinkedList`1.InternalInsertNodeBefore(LinkedListNode`1 node, LinkedListNode`1 newNode) 
    at System.Collections.Generic.LinkedList`1.AddLast(T value) 
    at MyProg.Pool.Worker() 
    at System.Threading.ThreadHelper.ThreadStart_Context(Object state) 
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) 
    at System.Threading.ThreadHelper.ThreadStart() 

나는 내가 다시이 오류를 얻을 수 없습니다 생각으로이 원인을 알아낼 수 없습니다입니다. 이 문제를 해결하는 방법에 대한 제안 사항이 있으십니까?

+0

스택 추적에서 'this._workers.AddLast (Thread.CurrentThread);'를 범인으로 지정합니다. C#에서 LinkedLists로 많은 작업을하지는 못했지만 아마도 순서대로 작동하여 스레드로부터 안전하지 않을 수 있습니다. – TyCobb

+1

'NullReferenceException '의 거의 모든 경우는 같습니다. 일부 힌트는 "[.NET의 NullReferenceException은 무엇입니까?] (http://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-in-net)"를 참조하십시오. –

+0

당신이 처음 초기화하지 않았거나 올바르게 생성 된 객체가 아니므로 NULLReferenceException 오류가 발생한다고 생각합니다. 'new '를 사용하여 객체를 생성 해보십시오 - 도움이 될만한 질문이 있습니다. –

답변

3

_workers 연결된 목록에 대한 액세스 권한이 제대로 동기화되지 않았습니다. 이 시나리오를 고려해보십시오.

어떤 지점에서 this._workets 목록에 하나의 항목이 있다고 가정 해 봅시다.

첫 번째 스레드는 this._workers.AddLast(Thread.CurrentThread); 호출하지만 아주 특별한 장소에서 중단됩니다 - AddLast() 방법 내부 :

public void AddLast(LinkedListNode<T> node) 
{ 
    this.ValidateNewNode(node); 
    if (this.head == null) 
    { 
     this.InternalInsertNodeToEmptyList(node); 
    } 
    else 
    { 
     // here we got interrupted - the list was not empty, 
     // but it would be pretty soon, and this.head becomes null 
     // InternalInsertNodeBefore() does not expect that 
     this.InternalInsertNodeBefore(this.head, node); 
    } 
    node.list = (LinkedList<T>) this; 
} 

다른 스레드가 this._workers.RemoveFirst();를 호출합니다. 그 명령문 주위에 아무런 lock()이 없으므로 이제 완료되고 목록은 비어 있습니다. AddLast()은 이제 InternalInsertNodeToEmptyList(node);으로 전화해야하지만 조건이 이미 평가 된대로 할 수 없습니다.

단순한 lock(this._tasks)을 단일 this._workers.AddLast() 행 주위에두면 그러한 시나리오를 방지해야합니다.

다른 나쁜 시나리오는 동일한 목록에 동시에 두 개의 스레드를 추가하는 것을 포함합니다.

+0

+1 시간 내 주셔서 감사합니다. 문 안쪽에 자물쇠를 넣어 문제를 해결했습니다. – Legend

3

생각났습니다. 코드 샘플은

private void Worker() 
{ 
    Action task = null; 
    while (true) // loop until threadpool is disposed 
    { 
     lock (this._tasks) // finding a task needs to be atomic 
     { 
      while (true) // wait for our turn in _workers queue and an available task 
      { 
      .... 
      } 
     } 

     task(); // process the found task 
     this._workers.AddLast(Thread.CurrentThread); 
     task = null; 
    } 
} 

잠금 lock()이는 lock에 싸여, 확장 또는 LinkedList (Pool.QueueTask를) 수정 다른 코드를 보면 this._workers.AddLast(Thread.CurrentThread);

감싸해야 놓쳤다.

+0

+1 감사합니다. 이것은 문제를 해결 한 것 같습니다. 나는 이것을 다시 관찰하면 갱신 할 것이다. – Legend

관련 문제