我正面临BackgroundWorker
和ObservableCollection
的跨线程问题。我知道为什么会出现问题,但我不知道我需要做什么或者哪种方法对我更好。我在C#和Threads上有点新鲜。
我发现了一些类似的问题,但总是在View上开发。我在ViewModel层面对它。
我正在使用WinForms
,DevExpress
和MVVM为Retail AX for Retail POS开发表单。
我的问题是,我需要在ObservableCollection
事件中向ProgressChanged
添加新项目。 ObservableCollection
是已发生的步骤/事件的列表。
但是,如果发生异常,则必须由DoWork
事件捕获异常。处理异常时,我必须添加一个新项ObservableCollection
。当我添加新项时,会引发跨线程异常。
这是一个紧凑的例子(它是一个压缩代码)。
查看/表格:
public partial class frmTest : frmTouchBase
{
private Collection<ProcessStep> ProcessStepList { get; set; }
protected override void OnLoad(EventArgs e)
{
if (!this.DesignMode)
{
this.gridControlProcess.DataSource = this.ProcessStepList;
this.ViewModel = new TestViewModel();
this.ViewModel.ReportingRequest += ViewModel_ReportingRequest;
this.bindingSource.Add(this.ViewModel);
this.ViewModel.StartProcess();
}
base.OnLoad(e);
}
private void ViewModel_ReportingRequest(object sender, EventArgs e)
{
this.ProcessStepList = ((TestViewModel)sender).ProcessStepList;
this.gridControlProcess.BeginInvoke(
new MethodInvoker(
delegate
{
this.gridControlProcess.DataSource = this.ProcessStepList;
}
)
);
}
}
视图模型:
public class TestViewModel : INotifyPropertyChanged
{
public ObservableCollection<ProcessStep> ProcessStepList = new ObservableCollection<ProcessStep>();
private System.ComponentModel.BackgroundWorker backgroundWorker;
private TestAsyncModel TestAsyncModel;
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler ReportingRequest;
public TestViewModel()
{
this.TestAsyncModel = new TestAsyncModel(this.Receber);
this.TestAsyncModel.PropertyChanged += TestAsyncModel_PropertyChanged;
this.ProcessStepList.CollectionChanged += ProcessStepList_CollectionChanged;
}
public void StartProcess()
{
this.backgroundWorker = new System.ComponentModel.BackgroundWorker();
this.backgroundWorker.DoWork += backgroundWorker_DoWork;
this.backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
this.backgroundWorker.WorkerReportsProgress = true;
this.backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
try
{
TestAsyncModel.Start();
}
catch (Exception exception)
{
/** a few process are executed here **/
/** here is the problem **/
this.ProcessStepList.Add(new ProcessStep() {
DateTime = DateTime.Now,
Detail = String.Format("ERRO - " + exception.Message)
});
}
}
void TestAsyncModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.backgroundWorker.ReportProgress(this.TestAsyncModel.Progress);
}
private void backgroundWorker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
if (
this.ProcessStepList.Count() == 0
|| this.ProcessStepList.Last().Detail != this.TestAsyncModel.LastMessage
) {
this.ProcessStepList.Add(new ProcessStep() { DateTime = DateTime.Now, Detail = this.TestAsyncModel.LastMessage });
}
}
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
void ProcessStepList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
this.OnReportingRequest();
}
protected void OnReportingRequest()
{
if (this.ReportingRequest != null)
{
this.ReportingRequest(this, EventArgs.Empty);
}
}
}
ProcessStep:
public class ProcessStep
{
public string Detail { get; set; }
public DateTime DateTime { get; set; }
}
TestAsyncModel
public class TestAsyncModel : INotifyPropertyChanged
{
#region interface attributes
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private string lastMessage;
public String LastMessage
{
get
{
return this.lastMessage;
}
internal set
{
if (this.lastMessage != value)
{
this.lastMessage = value;
this.NotifyPropertyChanged();
}
}
}
private int progress;
public int Progress
{
get
{
return this.progress;
}
internal set
{
if (this.progress != value)
{
this.progress = value;
this.NotifyPropertyChanged();
}
}
}
public void Start()
{
this.LastMessage = "Started";
this.Progress = 20;
this.LastMessage = "Do something...";
this.Progress = 40;
this.LastMessage = "Do something more...";
this.Progress = 60;
throw new Exception("An exception has been raised");// just as an example
}
#region interface methods
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
当我在方法TestViewModel
上使用代码this.ProcessStepList.Add()
时,问题发生在backgroundWorker_DoWork
上。
我尝试使用lock
,但没有成功。在GUI上,我使用BeginInvoke
来解决问题(正如您在方法ViewModel_ReportingRequest
上看到的那样),但是当我必须在ViewModel上执行非线程安全时我能做什么?