线程完成时与UI线程通信

时间:2013-08-23 18:35:19

标签: c# multithreading

我在下面有一个简单的程序,有2个线程执行某项任务。

Thread1是数据馈送器。 Thread2是数据处理器。

到目前为止,通过我的方法完成的工作正在进行,但我想在工作完成时获得更好的通知方式

这是代码

class Program 
{
    private static BlockingCollection<int> _samples = new BlockingCollection<int>();
    private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
    private static bool _cancel;


    static void Main(string[] args)
    {
        ThreadStart thread1 = delegate
        {
            ProcessThread1();
        };
        new Thread(thread1).Start();

        ThreadStart thread2 = delegate
        {
            ProcessThread2();
        };
        new Thread(thread2).Start();

        Console.WriteLine("Press any key to cancel..");

        Console.Read();

        _cancel = true;
        _cancellationTokenSource.Cancel();

        Console.Read();

    }

    private static void ProcessThread1()
    {
        for (int i = 0; i < 10; i++)
        {
            if (_cancel)
            {
                break;
            }

            Console.WriteLine("Adding data..");

            _samples.TryAdd(i,100);

            Thread.Sleep(1000);
        }

        // I dont like this. Instead can I get notified in the UI thread that this thread is complete.
        _cancel = true;
        _cancellationTokenSource.Cancel();

    }

    private static void ProcessThread2()
    {
        while (!_cancellationTokenSource.IsCancellationRequested)
        {
            int data;

            if (_samples.TryTake(out data, 100)) 
            {
                // Do some work.    
                Console.WriteLine("Processing data..");
            }

        }

        Console.WriteLine("Cancelled.");
    }


}

如果用户请求取消或工作完成,我希望程序退出。

我不确定如何在ProcessThread1用完工作时收到通知。目前我在工作完成时设置cancel = true但似乎不对。任何帮助表示赞赏。

2 个答案:

答案 0 :(得分:2)

如果您使用Task而不是手动创建线程,则可以在任务上附加 continuation ,以通知您的UI工作已完成。

Task workOne = Task.Factory.StartNew( () => ProcessThread1());

workOne.ContinueWith(t =>
{
     // Update UI here
}, TaskScheduler.FromCurrentSynchronizationContext());

使用.NET 4.5,这变得更加容易,因为您可以使用新的async语言支持:

var workOne = Task.Run(ProcessThread1);
var workTwo = Task.Run(ProcessThread2);

// asynchronously wait for both tasks to complete...
await Task.WhenAll(workOne, workTwo);

// Update UI here.

请注意,这两者都是在设计时考虑了用户界面 - 并且在控制台应用程序中表现异常,因为控制台应用程序中没有当前的同步上下文。当您将其移动到真正的用户界面时,它将表现正常。

答案 1 :(得分:1)

再启动一个线程,其唯一的工作是等待控制台输入:

private void ConsoleInputProc()
{
    Console.Write("Press Enter to cancel:");
    Console.ReadLine();
    _cancellationTokenSource.Cancel();
}

然后你的主线程启动两个处理线程和输入线程。

// create and start the processing threads
Thread t1 = new Thread(thread1);
Thread t2 = new Thread(thread2);
t1.Start();
t2.Start();

// create and start the input thread
Thread inputThread = new Thread(ConsoleInputProc);
inputThread.Start();

然后,您等待两个处理线程:

t1.Join();
// first thread finished. Request cancellation.
_cancellationTokenSource.Cancel();
t2.Join();

因此,如果用户按Enter,则输入线程设置取消标志。 thread1和thread2都看到取消请求并退出。

如果thread1完成其工作,则主线程设置取消标志,thread2将取消。

在任何一种情况下,程序都不会退出,直到线程2退出。

无需显式终止输入线程。程序退出时会死掉。

顺便说一句,我会从线程1 proc中删除这些行:

    // I dont like this. Instead can I get notified in the UI thread that this thread is complete.
    _cancel = true;
    _cancellationTokenSource.Cancel();

我会完全删除_cancel变量,并让第一个线程检查IsCancellationRequested就像第二个线程一样。

不幸的是,您必须启动一个专用线程来等待控制台输入,但这是我知道实现此目的的唯一方法。 Windows控制台似乎没有可等待的事件。

请注意,您可以使用Task执行相同操作,整体更易于使用。任务执行的代码是相同的。

更新

从更大的角度来看,我发现你有BlockingCollection的典型生产者/消费者设置。您可以使您的生产者和消费者线程更清洁:

private static void ProcessThread1()
{
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine("Adding data..");
        _samples.TryAdd(i, Timeout.Infinite, _cancellationTokenSource.Token);
        // not sure why the sleep is here
        Thread.Sleep(1000);
    }
    // Marks the queue as complete for adding.
    // When the queue goes empty, the consumer will know that
    // no more data is forthcoming.
    _samples.CompleteAdding();
}

private static void ProcessThread2()
{
    int data;
    while (_samples.TryTake(out data, TimeSpan.Infinite, _cancellationTokenSource.Token))
    {
        // Do some work.    
        Console.WriteLine("Processing data..");
    }

    Console.WriteLine("Cancelled.");
}

你仍然需要那个输入线程(除非你想在Console.KeyAvailable上旋转循环),但这大大简化了你的制作人和消费者。