WPF多任务处理以及计时器和后台工作者

时间:2013-08-30 09:44:40

标签: wpf multithreading task-parallel-library multitasking

几天前,我开始在WPF应用程序中使用任务进行并行化。我的应用程序需要每5秒执行一些工作。这项工作必须由4个任务并行化。除此之外,还需要实现后台工作程序,以避免在按任务执行工作时冻结UI。我找到了很多例子来了解任务是如何工作的。但是,我找不到任何简单的例子来理解任务如何与计时器,后台工作人员和锁定一起工作。我根据自己的理解写了一个简单的例子。请给我一些关于我是否正确行事的建议。这样,我将更好地理解WPF中的多任务处理。我期待着你的回复。

   
       namespace timer_and_thread
            {
                /// <summary>
                /// Interaction logic for MainWindow.xaml
                /// </summary>
                public partial class MainWindow : Window
                {
                    DispatcherTimer TimerObject;
                    Task[] tasks;
                    readonly object _countLock = new object();
                    int[] Summ = new int[10];
                    int Index = 0;
                    int ThreadsCounter = 0;


                    public MainWindow()
                    {
                        InitializeComponent();
                        TimerObject = new DispatcherTimer();
                        TimerObject.Tick += new EventHandler(timer_Elapsed);
                        TimerObject.Interval = new TimeSpan(0, 0, 5);
                    }


                    // call the method every 5 seconds
                    private void timer_Elapsed(object sender, EventArgs e)
                    {
                        TimerObject.Stop();

                        // 4 - is the tasks' count
                        ThreadsCounter = 4;
                        Index = 0;

                        tasks = new Task[4];
                        tasks[0] = Task.Factory.StartNew(() => DoSomeLongWork());
                        tasks[1] = Task.Factory.StartNew(() => DoSomeLongWork());
                        tasks[2] = Task.Factory.StartNew(() => DoSomeLongWork());
                        tasks[3] = Task.Factory.StartNew(() => DoSomeLongWork());

                         // wait untill all tasks complete
                         while (ThreadsCounter != 0) ;


                         TimerObject.Start();

                    }


                    private void DoSomeLongWork()
                    {
                        while (Index < Summ.Length)
                        {


                            // lock the global variable from accessing by multiple threads at a time
                        int localIndex = Interlocked.Increment(ref Index) - 1;

                            //I wrote rundom number generation just a an example of doing some calculation and getting some result. It can also be some long calculation. 
                            Random rnd = new Random();
                            int someResult = rnd.Next(1, 100000);

                            // lock the global variable (Summ) to give it the result of calculation 
                            lock (_countLock)
                            {
                                Summ[localIndex] = someResult;
                            }      
                        }


                        Interlocked.Decrement(ref ThreadsCounter);
                        return;

                    }

                    // button by which I start the application working
                    private void Start_Button_Click_1(object sender, RoutedEventArgs e)
                    {
                        TimerObject.Start();
                    }

                }
            }

我还有两个问题:

  1. 我可以使用任务代替后台工作人员吗?

  2. 正如我所知,锁定用于防止线程访问全局变量。但是,要通过线程访问UI元素,我应该使用Dispatcher。右

  3. Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
        {
    
    label1.Content = "Some text";
    }));
    

2 个答案:

答案 0 :(得分:4)

在我看来你似乎甚至不需要BackgroundWorker。目前你有:

private void timer_Elapsed(object sender, EventArgs e)
{
    TimerObject.Stop();

    BackgroundWorker backgroundWorkerObject = new BackgroundWorker();
    backgroundWorkerObject.DoWork += new DoWorkEventHandler(StartThreads);
    backgroundWorkerObject.RunWorkerAsync();

    TimerObject.Start();
}


private void StartThreads(object sender, DoWorkEventArgs e)
{
    tasks = new Task[4];
    tasks[0] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[1] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[2] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[3] = Task.Factory.StartNew(() => DoSomeLongWork());

    // Give the tasks a second to start.
    Thread.Sleep(1000);
}

所有BackgroundWorker都是启动线程然后由于某种未知原因等待一秒钟才继续。在看到BackgroundWorker退出之前,我没有看到任何特殊原因让你等一下。

如果删除了不必要的延迟,则可以将代码更改为:

private void timer_Elapsed(object sender, EventArgs e)
{
    TimerObject.Stop();

    tasks = new Task[4];
    tasks[0] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[1] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[2] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[3] = Task.Factory.StartNew(() => DoSomeLongWork());

    TimerObject.Start();
}

您遇到的另一个问题是,如果在计时器过去事件再次发生之前这些任务没有完成,您将覆盖Tasks数组和您的计算(Sum数组,特别是)将包含来自两组计算的信息。如果这些线程花费的时间超过五秒钟,那么在这里使用全局变量会让你遇到麻烦。

我将假设你的DoSomeLongWork方法只是一个占位符,并且不会批评它。不过,我会提到,为了增加共享变量,不需要锁定。而不是:

int localIndex;
// lock the global variable from accessing by multiple threads at a time
lock (_countLock)
{
    localIndex = Index;
    Index++;
}

你可以写:

localIndex = Interlocked.Increment(ref Index, 1) - 1;

Interlocked.Increment以原子方式递增值并返回新值。

答案 1 :(得分:1)

要回答8月30日之后的问题,您可以查看Tasks的ContinueWhenAll函数而不是while循环,并将计时器的开头移动到该函数。它需要一个任务列表,然后当所有任务完成后,创建另一个任务并运行它。这应该可以防止用户界面冻结,但如果它必须每5秒运行一次,而不是在完成上一个任务后每5秒运行一次,那么您可能需要查看不同的内容。

http://msdn.microsoft.com/en-us/library/dd321473%28v=vs.110%29.aspx