2010-03-23 6 views
2

У меня есть следующий метод:Использование ThreadPool.QueueUserWorkItem - нить неожиданно завершает работу

public void PutFile(string ID, Stream content) 
    { 
     try 
     { 
      ThreadPool.QueueUserWorkItem(o => putFileWorker(ID, content)); 
     } 

     catch (Exception ex) 
     { 
      OnPutFileError(this, new ExceptionEventArgs { Exception = ex }); 
     } 
    } 

Метод putFileWorker выглядит следующим образом:

private void putFileWorker(string ID, Stream content) 
    { 
     //Get bucket name: 
     var bucketName = getBucketName(ID) 
      .ToLower(); 

     //get file key 
     var fileKey = getFileKey(ID); 

     try 
     { 
      //if the bucket doesn't exist, create it 
      if (!Amazon.S3.Util.AmazonS3Util.DoesS3BucketExist(bucketName, s3client)) 
       s3client.PutBucket(new PutBucketRequest { BucketName = bucketName, BucketRegion = S3Region.EU }); 

      PutObjectRequest request = new PutObjectRequest(); 
      request.WithBucketName(bucketName) 
       .WithKey(fileKey) 
       .WithInputStream(content); 

      S3Response response = s3client.PutObject(request); 
      var xx = response.Headers; 

      OnPutFileCompleted(this, new ValueEventArgs { Value = ID }); 
     } 

     catch (Exception e) 
     { 
      OnPutFileError(this, new ExceptionEventArgs { Exception = e }); 
     } 
    } 

Я создал небольшую консоль приложение, чтобы проверить это. Я подключаю обработчики событий для событий OnPutFileError и OnPutFileCompleted.

Если я вызываю свой метод PutFile и вступаю в это, он переходит к строке «// если строка не существует, создайте ее», а затем завершается. Никаких исключений, ошибок нет, ничего. Он не завершается (я также установил точки останова на своих обработчиках событий) - он просто выходит.

Если я запускаю тот же метод без ThreadPool.QueueUserWorkItem, то он работает отлично ...

ли я что-то отсутствует?

+0

Действительно ли это прекращает обработку, или вы получаете переключатель потоков в среде отладки? Закрывается ли ваш mainthread? (это будет Thread.Abort() все рабочие потоки) –

+0

хорошо ... он не ударил точки останова после вышеупомянутой строки ... он просто попадает в конец статического метода void Main() ... – Alex

+0

i также реализовано это: http://statichippo.com/archive/2009/11/09/run-generic-tasks-async-fluent-ly.aspx : var async = new AsyncQueueManager(); async.Queue (o => putFileWorker (ID, content)); имеет тот же результат! – Alex

ответ

8

ThreadPool нити являются фоновыми потоками (см. Ссылку). Они не будут запускать ваше приложение, если основной поток выйдет.

Как правило, в приложениях WinForms это не проблема, потому что основной поток пользовательского интерфейса вызывает Application.Run и начинает обработку событий. Для вашего консольного приложения, если ваш основной метод не дождался завершения рабочего элемента, основной поток будет стоять в очереди на рабочий элемент и затем выйти.

Вы можете создать фоновый поток самостоятельно и установить его свойство IsBackground в значение false. Или вы можете создать поток и позвонить Thread.Join, чтобы дождаться его завершения.

- EDIT -

Как указывается в комментариях ниже, вы можете также использовать ManualResetEvent, или даже пользовательский класс синхронизации в соответствии с предложением Линик. Цель состоит в том, чтобы заблокировать основной поток до тех пор, пока потоки фона не будут завершены.

Чтобы использовать ManualResetEvent, создайте его в своем основном потоке и передайте его в качестве аргумента. (Я поручу его статической переменной здесь только для краткости.)

ManualResetEvent s_WaitEvent; 

ManualResetEvent s_WaitEvent = new ManualResetEvent(false); // non-signaled 
// queue work item here 
s_WaitEvent.WaitOne(); 

В конце вашего рабочего потока, сигнализируют событие: CountDownLatch

s_WaitEvent.Set(); 

Линка хорошо, если у вас есть много потоки, которые должны обрабатываться, прежде чем вы сможете выйти. Вы также можете использовать отдельные ManualResetEvents для каждого потока и ждать, пока все они будут выполнены, используя WaitHandle.WaitAll(WaitHandle[]). (ManualResetEvent наследуется от WaitHandle.)

+1

Он хотел бы установить 'IsBackground = false', чтобы он продолжал работать. –

+0

Исправлено, спасибо. –

+0

Спасибо, это то, что происходит. Пожалуйста, не могли бы вы подробно остановиться на IsBackground ... что мне нужно делать? – Alex

1

Поместите Console.ReadLine() в свой Основной поток, чтобы заблокировать его, пока вы тестируете свою рабочую нить. Это позволит сохранить основной выход. Просто нажмите «Ввод», когда закончите.

+1

Thread.Join лучше, так как он не требует взаимодействия с пользователем. –

+0

Вы не можете 'Thread.Join()' поток в ThreadPool, или, по крайней мере, вы не должны. –

+0

Это может сработать для игрушечного кода, но если я клиент, я буду серьезно злиться, если результат вашей программы зависит от того, когда я попаду в игру! –

0

Используйте CountDownLatch, чтобы заставить основные ждать все потоков, которые вы выстраивались в очередь:

public class CountDownLatch 
{ 
    private int m_remain; 
    private EventWaitHandle m_event; 

    public CountDownLatch (int count) 
    { 
     if (count < 0) 
      throw new ArgumentOutOfRangeException(); 
     m_remain = count; 
     m_event = new ManualResetEvent(false); 
     if (m_remain == 0) 
     { 
      m_event.Set(); 
     } 
    } 

    public void Signal() 
    { 
     // The last thread to signal also sets the event. 
     if (Interlocked.Decrement(ref m_remain) == 0) 
      m_event.Set(); 
    } 

    public void Wait() 
    { 
     m_event.WaitOne(); 
    } 
} 

В Main:

static void Main(string[] args) 
{ 
    CountDownLatch latch = new CountDownLatch(numFiles); 
    // 
    // ... 
    // 
    putFileWorker("blah", streamContent); 
    // 
    // ... 
    // 

    // waits for all of the threads to signal 
    latch.Wait(); 
} 

В методе работника:

private void putFileWorker(string ID, Stream content) 
{ 
    try 
    { 
     //Get bucket name: 
     var bucketName = getBucketName(ID) 
      .ToLower(); 

     //get file key 
     var fileKey = getFileKey(ID); 

     try 
     { 
      //if the bucket doesn't exist, create it 
      if (!Amazon.S3.Util.AmazonS3Util.DoesS3BucketExist(bucketName, s3client)) 
       s3client.PutBucket(new PutBucketRequest { BucketName = bucketName, BucketRegion = S3Region.EU }); 

      // 
      // ... 
      // 
     } 

     catch (Exception e) 
     { 
      OnPutFileError(this, new ExceptionEventArgs { Exception = e }); 
     } 
    } 
    finally 
    { 
     latch.Signal(); 
    } 
}