0

几天前,我开始在我的 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。正确的?

Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
    {

label1.Content = "Some text";
}));
4

2 回答 2

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特别是数组)将包含来自两组计算的信息。如果这些线程的完成时间超过 5 秒,那么您在这里使用全局变量将会给您带来麻烦。

我将假设您的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以原子方式递增值并返回新值。

于 2013-08-30T18:44:17.717 回答
1

要回答 8 月 30 日的问题,您可以查看 Tasks 的 ContinueWhenAll 函数,而不是 while 循环,并将计时器的开始移至该函数。它需要一个任务列表,然后当所有任务都完成后,创建另一个任务并运行它。这应该可以防止你的 UI 冻结,尽管如果它必须每 5 秒运行一次,而不是在完成之前的任务后每 5 秒运行一次,你可能需要研究一些不同的东西。

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

于 2014-09-10T15:13:05.613 回答