如何检查.NET BlockingCollection状态并等待完成

本文关键字:状态 等待 BlockingCollection NET 何检查 检查 | 更新日期: 2023-09-27 17:57:35

我有一个C#工作线程,它使用BlockingCollection将一批相机位图保存到光盘中。它工作得很好,但我需要一个从主应用程序调用的方法,该方法会阻止执行,直到所有排队的位图都保存下来(例如,请参阅消息末尾)。

整个班级看起来像:

namespace GrabGUI
{
    struct SaveTask
    {
        public string fname;
        public Bitmap bm;
    }
    class ImageWriter
    {
        private BlockingCollection<SaveTask> queue = new BlockingCollection<SaveTask>();
        //resets when read
        public string ErrorsOccurred;
        private Thread writerthread;
        public ImageWriter()
        {
            writerthread = new Thread(new ThreadStart(Writer));
            writerthread.Start();
        }

        public void Stop()
        {
            queue.CompleteAdding();
        }
        public string WaitForIdleAndGetErrors()
        {
            //HOW TO WAIT FOR QUEUE TO GET PROCESSED?
            return ErrorsOccurred;
        }
        public void AddImageToQueue(string filename, Bitmap bmap)
        {
            SaveTask t;
            t.bm=bmap;
            t.fname=filename;
            queue.Add(t);
        }
        void Writer()
        {
            while (queue.IsCompleted==false)
            {
                try
                {
                    SaveTask t = queue.Take();// blocks when the queue is empty
                    SaveBitmap(t.fname, t.bm);
                }
                catch (Exception e)
                {
                    //comes here after called Stop
                    return;
                }
            }
        }

        private void SaveBitmap(string filename,Bitmap m_bitmap)
        {
            //saving code
        }
    }
}

并且从主要应用程序中使用,如:

ImageWriter w=new ImageWriter();
w.AddImageToQueue(fname,bitmap);//repeat many times
...
//wait until whole queue is completed and get possible errors that occurred
string errors=w.WaitForIdleAndGetErrors();

因此,问题是如何实现对WaitForIdleAndGetErrors()的阻塞等待。有什么建议吗?

如何检查.NET BlockingCollection状态并等待完成

这里有一个非常简单的方法:

public string WaitForIdleAndGetErrors()
{
    while (queue.IsCompleted == false )
    {
       System.Threading.Thread.Current.Sleep(100);
    }
   return ErrorsOccurred;
}

或者使用手动重置事件Slim:

声明新实例var:

ManualResetEventSlim _mre = new ManualResetEventSlim(false);
public string WaitForIdleAndGetErrors()
{
    if (queue.IsCompleted == false )
    {
       _mre.Wait();
    }
   return ErrorsOccurred;
}

然后,当您的队列完成时,向mre发出信号。

_mre.Set();   // this will release any thread waiting.

最后,当添加一个项目进行处理时,您需要Reset() _mre,这将导致任何Wait()阻塞,直到_mre被发送信号(通过Set()

需要考虑的事项

如果你用UI线程调用它,那么所有的UI交互都将显示为冻结,你最好使用定时器来轮询或类似的东西,否则你会有糟糕的UI体验。

但是,您可以使用BackgroundWorkerInvoke(UI线程将在完成后处理的方法/事件)来启动整个过程。

当队列为空时,线程将退出。因此,您的WaitForIdleAndGetErrors方法只需要等待线程结束。Thread.Join就是这么做的:

    public string WaitForIdleAndGetErrors()
    {
        // Wait for thread to exit
        writerthread.Join();
        return ErrorsOccurred;
    }

Thread.Join进行非繁忙等待。在等待线程退出时,您不会消耗CPU,也不需要单独的事件。

顺便说一句,你可以利用BlockingCollection.GetConsumingEnumerable:来简化你的线程

    void Writer()
    {
        foreach (SaveTask t in queue.GetConsumingEnumerable())
        {
            try
            {
                SaveBitmap(t.fname, t.bm);
            }
            catch (Exception e)
            {
                //comes here after called Stop
                return;
            }
        }
    }

要添加到前面的答案中,如果您想以非阻塞的方式(Thread.Join正在阻塞)或想给调用者更多的控制权,您可以将以下内容添加到ManualResetEventSlim示例中:

    ManualResetEventSlim _mre = new ManualResetEventSlim(false);
    public Task<string> WaitForIdleAndGetErrors()
        {
            return Task.Factory.StartNew(() =>
            {
                if (!_queue.IsCompleted)
                {
                    _mre.Wait();
                }
                return ErrorsOccurred;
            });
        }

然后,除了UI(假设)线程空闲之外,您还可以控制是否要阻止调用、何时阻止任务以及如何阻止长的可能的呼叫可能是:

// Calling Thread
...
imageWriter.WaitForIdleAndGetErrors.Wait(myDesiredWaitLimit);
...