在MVVM上的BackgroundWorker跨线程异常

时间:2017-06-22 23:18:07

标签: c# winforms mvvm backgroundworker

我正面临BackgroundWorkerObservableCollection的跨线程问题。我知道为什么会出现问题,但我不知道我需要做什么或者哪种方法对我更好。我在C#和Threads上有点新鲜。

我发现了一些类似的问题,但总是在View上开发。我在ViewModel层面对它。

我正在使用WinFormsDevExpress和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上执行非线程安全时我能做什么?

0 个答案:

没有答案