使用ThreadPool.QueueUserWorkItem - 线程意外退出

Ale*_*lex 2 c# multithreading asynchronous

我有以下方法:

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

        catch (Exception ex)
        {
            OnPutFileError(this, new ExceptionEventArgs { Exception = ex });
        }
    }
Run Code Online (Sandbox Code Playgroud)

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 });
        }
    }
Run Code Online (Sandbox Code Playgroud)

我已经创建了一个小控制台应用程序来测试它.我为OnPutFileError和OnPutFileCompleted事件连接了事件处理程序.

如果我调用我的PutFile方法,并进入此步骤,它将进入"//如果存储桶不存在,创建它"行,然后退出.没有例外,没有错误,没有.它没有完成(我也在我的事件处理程序上设置了断点) - 它只是退出.

如果我在没有ThreadPool.QueueUserWorkItem的情况下运行相同的方法,那么它运行良好...

我错过了什么吗?

Pau*_*ams 8

ThreadPool线程是后台线程(请参阅链接).如果主线程退出,它们将不会保持应用程序运行.

通常,在WinForms应用程序中,这不是问题,因为主UI线程调用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();
Run Code Online (Sandbox Code Playgroud)

在工作线程结束时,发出事件信号:

s_WaitEvent.Set();
Run Code Online (Sandbox Code Playgroud)

Link的CountDownLatch很好,如果你有许多线程必须处理才能退出.您还可以为每个线程使用单独的ManualResetEvent,并使用WaitHandle.WaitAll(WaitHandle [])等待它们全部完成.(ManualResetEvent继承自WaitHandle.)