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

时间:2014-04-08 14:11:30

标签: c# multithreading

我有一个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()的阻塞等待。有什么建议吗?

3 个答案:

答案 0 :(得分:4)

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

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

   return ErrorsOccurred;
}

或使用ManualResetEventSlim:

声明新实例var:

ManualResetEventSlim _mre = new ManualResetEventSlim(false);

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

   return ErrorsOccurred;
}

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

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

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

需要考虑的事项

如果你用UI线程调用它,那么所有UI交互都会被冻结,你最好使用Timer进行轮询或类似的东西,否则你的UI体验会很糟糕。

但是,您可以使用BackgroundWorker然后Invoke在完成后处理UI线程的方法/事件来解决这一切。

答案 1 :(得分:4)

当队列为空时,线程将退出。所以你的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;
            }
        }
    }

答案 2 :(得分:0)

要添加到以前的答案,如果要以非阻塞方式(Thread.Join阻止)或想要给调用者提供更多控制,可以将以下内容添加到ManualResetEventSlim示例中: / p>

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

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

    if (choice1 == 4)

    {
            nbGamesEasy_Finished++;

            successRate_Easy = nbGamesEasy_Finished / nbGames_Easy * 100;

            System.out.println("How many time did you take to fill the grid ?");

            System.out.println("The time must be in minutes");

            String display5 = sudoku.nextLine();

            resolutionTime_easy = (nbGamesEasy_Finished * resolutionTime_easy) + Integer.parseInt(display5) / (nbGamesEasy_Finished);
    }

    else if (choice1 == 5)
    {
            successRate_Easy = nbGamesEasy_Finished - 1 / nbGames_Easy * 100;
    }

// and for default, use else

    else
    {
        //this is the default statement
        //you can do what you want here if not 4 or 5
    }