我是C#编程的新手。 我试图使用后台工作程序从服务器列表中获取更新的数量。每个服务器的结果都显示在报告进度方法的列表视图中。 我能够使用foreach循环成功获得结果,但在尝试使用并行foreach获得相同结果时,listview的所有列和行都会混淆。
例如: foreach循环的输出: 服务器名称状态更新可用
并行foreach的输出:
我已尝试锁定部分代码,并尝试使用并发包,但无法解决问题。以下是parallelforeach代码。我做错了什么?任何建议都会有很大的帮助。
Parallel.ForEach(namelist, /*new ParallelOptions { MaxDegreeOfParallelism = 4 }, */line =>
//foreach (string line in namelist)
{
if (worker.CancellationPending)
{
e.Cancel = true;
worker.ReportProgress(SysCount, obj);
}
else
{
this.SystemName = line;//file.ReadLine();
Status.sVariables result = new Status.sVariables();
result = OneSystem(this.SystemName);
switch (result.BGWResult)
{
case -1:
this.StatusString = "Login to server failed!";
break;
//other status are assigned here;
}
SysCount++;
bag.Add(this);
}
Status returnobj;
bag.TryTake(out returnobj);
worker.ReportProgress(SysCount, returnobj);
Thread.Sleep(200);
});
ReportProgress方法:
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (!backgroundWorker1.CancellationPending)
{
Status result = (Status)e.UserState;
Complete_label.Visible = true;
if (listView1.InvokeRequired)
listView1.Invoke(new MethodInvoker(delegate
{
listView1.Items.Add("");
listView1.Items[result.SysCount - 1].SubItems.Add(result.SystemName);
listView1.Items[result.SysCount - 1].SubItems.Add(result.StatusString);
listView1.Items[result.SysCount - 1].SubItems.Add(result.AvailableUpdatesCount.ToString());
}));
else
{
try
{
listView1.Items.Add("");
listView1.Items[result.SysCount - 1].SubItems.Add(result.SystemName);
listView1.Items[result.SysCount - 1].SubItems.Add(result.StatusString);
listView1.Items[result.SysCount - 1].SubItems.Add(result.AvailableUpdatesCount.ToString());
}
catch (Exception ex)
{}
//other stuff
}
}
答案 0 :(得分:0)
您的结果全都混淆了,因为您使用并行操作写入全局状态,例如SystemName
和StatusString
,因此当您使用时,这些全局变量的内容最终会混淆尝试阅读和打印他们的价值观。
你可以引入一个lock
,但这会完全打败Parallel.ForEach
。因此,要么放弃使用Parallel.ForEach
(在这种情况下似乎没有用处),要么需要收集数据并确保以线程安全的方式将其发送给报告者。
为了进一步解释,让我们检查一下代码:
this.SystemName = line; // <- the worker has now written to this, which is global to all workers
...
result = OneSystem(this.SystemName); // <- another worker may have overwritten SystemName at this point
...
this.StatusString = "Login to server failed!"; // <- again writing to shared variable
...
bag.Add(this); // <- now trying to "thread protect" already corrupted data
因此,如果必须并行运行循环,则每个工作者必须仅更新其自己的隔离数据,然后将其推送到GUI编组报告方法。
答案 1 :(得分:0)
真正的问题是ListView更新代码使用错误的索引来更新项目。它假定Status.SysCount
属性包含正确的索引。如果执行按顺序执行,则可能是这样,但如果执行并行运行则会失败 - 不同的线程可以以不同的速度完成并报告无序的进度。
只需使用ListViewItemCollection.Add
返回的ListViewItem对象即可解决实际问题private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (!backgroundWorker1.CancellationPending)
{
Status result = (Status)e.UserState;
Complete_label.Visible = true;
var newItem=listView1.Items.Add("");
newItem.SubItems.Add(result.SystemName);
newItem.SubItems.Add(result.StatusString);
newItem.SubItems.Add(result.AvailableUpdatesCount.ToString());
//other stuff
}
}
代码有更严重的问题 - State
类尝试并行处理数据,将数据存储在自己的属性中,然后发送自己报告。显然,显示的数据将始终在变化。
更好的选择是在循环中创建一个新的State实例,或者更好的是,仅为报告创建一个类:
class StatusProgress
{
public string SystemName{get;set;}
public string StatusString{get;set;}
public int AvailableUpdatesCount {get;set;}
}
....
int sysCount=0;
Parallel.ForEach(namelist, line =>
{
var progress=new StatusProgress();
progress.SystemName = line;//file.ReadLine();
Status.sVariables result = new Status.sVariables();
result = OneSystem(line);
switch (result.BGWResult)
{
case -1:
progress.StatusString = "Login to server failed!";
break;
//other status are assigned here;
}
var count=Interlocked.Increment(ref sysCount);
}
worker.ReportProgress(count, progress);
});
请注意,代替SysCount++
使用Interlocked.Increment
以原子方式增加值和获取递增值的副本。如果我没有这样做,多个线程可以在我有机会报告进度之前修改SysCount
。
进度报告代码将更改为使用StateProgress
StatusProgress result = (StatusProgress)e.UserState;
最后,BackgroundWorker已经过时,因为任务并行库以更轻量级的方式提供了BGW所做的一切以及更多功能。例如,您可以{@ 3}}使用CancellationToken,并使用cancel the parallel loop类以类型安全的方式报告进度。
.NET中的大多数异步方法都识别CancellationToken和Progress,这意味着您可以轻松地报告进度并取消异步任务{。{3}}。
代码可以像这样重写:
在UI表单上:
private void ReportServerProgress(StatusProgress result)
{
Complete_label.Visible = true;
var newItem=listView1.Items.Add("");
newItem.SubItems.Add(result.SystemName);
newItem.SubItems.Add(result.StatusString);
newItem.SubItems.Add(result.AvailableUpdatesCount.ToString());
//other stuff
}
CancellationTokenSource _cts;
Progress<StatusProgress> _progress;
public void StartProcessiong()
{
_cts=new CancellationTokenSource();
_progress=new Progress<StatusProgress(progress=>ReportServerProgress(progress);
StartProcessing(/*input*/,_cts.Token,_progress);
}
public void CancelLoop()
{
if (_cts!=null)
_cts.Cancel();
}
处理代码可以在同一表格或任何其他类别上。实际上,将UI与处理代码分开会更好,尤其是当您进行非平凡的处理时,例如调用每个服务器以确定其状态
public void StartProcessing(/*input parameters*/,
CancellationTokenSource token,
IProgress<StatusProgress> progress)
{
.....
var po=new ParallelOptions();
po.CancellationToken=token;
Parallel.ForEach(namelist, po,line =>
{
var status=new StatusProgress();
status.SystemName = line;//file.ReadLine();
Status.sVariables result = new Status.sVariables();
result = OneSystem(line);
switch (result.BGWResult)
{
case -1:
progress.StatusString = "Login to server failed!";
break;
//other status are assigned here;
}
progress.Report(status);
}
}
许多异步.NET方法接受取消令牌,因此您可以将其传递给Web Service调用,并确保取消循环和任何未完成的长调用。