Использование ThreadPool.QueueUserWorkItem - поток неожиданно завершает работу - PullRequest
2 голосов
/ 23 марта 2010

У меня есть следующий метод:

    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 , тогда он работает нормально ...

Я что-то упустил?

Ответы [ 3 ]

8 голосов
/ 23 марта 2010

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

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

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

- РЕДАКТИРОВАТЬ -

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

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

ManualResetEvent s_WaitEvent;

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

В конце вашего рабочего потока, сообщите событие:

s_WaitEvent.Set();

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

1 голос
/ 23 марта 2010

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

0 голосов
/ 23 марта 2010

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

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();
    }
}

В основном:

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();
    }
}
...