简体   繁体   English

可以使用任务并行库一起运行长时间运行的方法和“正在工作...”对话框以允许将长时间任务写入 BindingList 吗?

[英]Can a long-running method and a “Working…” dialog be run together using the Task Parallel Library to allow long task to write to a BindingList?

I have a WPF (C# and .NET 4) application that has a long running task in it that blocked the UI giving the impression that it has hung.我有一个 WPF(C# 和 .NET 4)应用程序,其中有一个长时间运行的任务,它阻止了 UI,给人一种它已经挂起的印象。 I decided to put this long-running task onto a separate thread by using a BackgroundWorker thread and showed a BusyIndicator in a separate popup window (named WorkingDialog below).我决定使用BackgroundWorker线程将这个长时间运行的任务放到一个单独的线程中,并在单独的弹出窗口 window(下面命名为 WorkingDialog)中显示一个BusyIndicator cator。 This worked fine until the long running task writes to a BindingList (which is bound to a grid on the UI), and I got the following exception:这工作正常,直到长时间运行的任务写入 BindingList(绑定到 UI 上的网格),我得到以下异常:

This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread这种类型的 CollectionView 不支持从与 Dispatcher 线程不同的线程对其 SourceCollection 的更改

This is (a very trimmed down version of) the business layer code...这是业务层代码(一个非常精简的版本)......

public class CustomMessage
{
    public DateTime TimeStamp { get; private set; }
    public string Message { get; private set; }

    public CustomMessage(string message)
    {
        Message = message;
        TimeStamp = DateTime.Now;
    }
}

public class MyRandomBusinessClass
{
    public BindingList<CustomMessage> LoggingList { get; private set; }

    public MyRandomBusinessClass()
    {
        LoggingList = new BindingList<CustomMessage>();
    }

    public void SomeLongRunningTask()
    {
        System.Threading.Thread.Sleep(5000);

        LoggingList.Add(new CustomMessage("Completed long task!"));
    }
}

... the UI... ...用户界面...

<Window x:Class="WpfApplication4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Height="350"
        Width="525">

    <StackPanel>
        <DataGrid x:Name="logGrid"
                  AutoGenerateColumns="True"
                  ItemsSource="{Binding Path=LoggingList}" />

        <Button Click="ProcessLongTask_Click"
                Content="Do Long Task" />
    </StackPanel>

</Window>

... The UI code... ...用户界面代码...

using System.ComponentModel;
using System.Windows;

namespace WpfApplication4
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MyRandomBusinessClass RandomBusinessClass { get; set; }
        public MainWindow()
        {
            InitializeComponent();

            RandomBusinessClass = new MyRandomBusinessClass();

            logGrid.DataContext = RandomBusinessClass;
        }

        private void ProcessLongTask_Click(object sender, RoutedEventArgs e)
        {
            ProcessTask1();
        }

        private void ProcessTask1()
        {
            WorkingDialog workingDialog = new WorkingDialog();
            workingDialog.Owner = this;

            BackgroundWorker worker = new BackgroundWorker();

            worker.DoWork += delegate(object s, DoWorkEventArgs args)
            {
                RandomBusinessClass.SomeLongRunningTask();
            };

            worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
            {
                workingDialog.Close();
            };

            worker.RunWorkerAsync();
            workingDialog.ShowDialog();
        }
    }
}

... the worker dialog... ...工人对话...

<Window xmlns:extToolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit/extended"
        x:Class="WpfApplication4.WorkingDialog"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        mc:Ignorable="d"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="Working Dialog"
        WindowStartupLocation="CenterOwner"
        Height="116"
        Width="199">

    <Grid>

        <extToolkit:BusyIndicator x:Name="busyIndicator"
                                  IsBusy="True"
                                  BusyContent="Working ..."
                                  HorizontalAlignment="Left"
                                  VerticalAlignment="Top" />

    </Grid>
</Window>

I've found several threads in this forum pointing to the work around by Bea Stollnitz ), but I am wondering if there is a way of executing the long running task, showing the WorkingDialog and then closing the dialog once the task is complete using the Task Parallel Library?我在这个论坛中发现了几个线程指向Bea Stollnitz的工作),但我想知道是否有一种方法可以执行长时间运行的任务,显示 WorkingDialog,然后在任务完成后使用任务并行库? Can the Task Parallel Library achieve the desired result without having to modify the business layer code?任务并行库能否在不修改业务层代码的情况下达到预期的效果?

Edit - Workaround 1编辑 - 解决方法 1

Thanks to comments from Ben below, there is a workaround if your long-running task is in the same assembly as the UI or uses the System.Windows namespace.感谢 Ben 下面的评论,如果您的长时间运行的任务与 UI 在同一个程序集中或使用 System.Windows 命名空间,则有一种解决方法。 Using the current application dispatcher I can add the message to my list without the exception:使用当前的应用程序调度程序,我可以毫无例外地将消息添加到我的列表中:

public class MyRandomBusinessClass
{
    public BindingList<CustomMessage> LoggingList { get; private set; }

    public MyRandomBusinessClass()
    {
        LoggingList = new BindingList<CustomMessage>();
    }

    public void SomeLongRunningTask()
    {
        System.Threading.Thread.Sleep(5000);

        Dispatcher myDispatcher = Application.Current.Dispatcher;
        myDispatcher.BeginInvoke((Action)(() => LoggingList.Add(new CustomMessage("Completed long task!"))));
    }
}

I'm still curious whether this would be possible using the Task Parallel Library or am I barking up the wrong tree?.我仍然很好奇这是否可以使用任务并行库或者我吠叫错误的树? In my actual application the long-running class is in a separate class library.在我的实际应用程序中,长期运行的 class 位于单独的 class 库中。

Edit 2编辑 2

I've accepted Ben's solution below as it solves my initial problem of getting the long-running task to run without blocking the UI or throwing the exception.我在下面接受了 Ben 的解决方案,因为它解决了我最初的问题,即在不阻塞 UI 或引发异常的情况下运行长时间运行的任务。 I appreciate that the original question was about the Task Parallel Library, so forgive me if you are reading this thinking that it has a TPL solution.我很欣赏最初的问题是关于任务并行库的,所以如果您正在阅读这篇文章并认为它具有 TPL 解决方案,请原谅我。

You need to use a dispatcher to add a new element to your logginglist.您需要使用 调度程序将新元素添加到日志列表中。

public void SomeLongRunningTask()
{
    System.Threading.Thread.Sleep(5000);

    Application.Current.Dispatcher.BeginInvoke((Action)(() => LoggingList.Add(new CustomMessage("Completed long task!"))));
}

Edit :编辑

public class MyRandomBusinessClass
{
  public BindingList<CustomMessage> LoggingList { get; private set; }
  public Action<MyRandomBusinessClass, CustomMessage> CallBackAction { get; private set; }

  public MyRandomBusinessClass(Action<MyRandomBusinessClass, CustomMessage> cba) 
  {
      LoggingList = new BindingList<CustomMessage>();
      this.CallBackAction = cba;
  }

  public void SomeLongRunningTask()
  {
    System.Threading.Thread.Sleep(5000);

    if (cba != null)
      CallBackAction(this, new CustomMessage("Completed long task!"));
  }
}

Then where you create your business class:然后在哪里创建您的业务 class:

MyRandomBusinessClass businessClass = new MyRandomBusinessClass((sender, msg) =>
   Application.Current.Dispatcher.BeginInvoke((Action)(() => 
      sender.LoggingList.Add(msg))));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM