几天前,我开始在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();
}
}
}
我还有两个问题:
我可以使用任务代替后台工作人员吗?
正如我所知,锁定用于防止线程访问全局变量。但是,要通过线程访问UI元素,我应该使用Dispatcher。右
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
{
label1.Content = "Some text";
}));
答案 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