【问题标题】: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?可以使用任务并行库一起运行长时间运行的方法和“工作...”对话框以允许长时间任务写入 BindingList?
【发布时间】:2011-06-07 10:00:11
【问题描述】:

我有一个 WPF(C# 和 .NET 4)应用程序,其中有一个长时间运行的任务,它阻塞了 UI,给人一种它已挂起的印象。我决定通过使用BackgroundWorker 线程将这个长时间运行的任务放到一个单独的线程中,并在一个单独的弹出窗口(下面命名为 WorkingDialog)中显示一个BusyIndicator。这工作正常,直到长时间运行的任务写入 BindingList(绑定到 UI 上的网格),我得到以下异常:

这种类型的 CollectionView 不支持从不同于 Dispatcher 线程的线程更改其 SourceCollection

这是(非常精简的)业务层代码...

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!"));
    }
}

...用户界面...

<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>

...用户界面代码...

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();
        }
    }
}

...工人对话...

<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>

我在这个论坛中发现了几个线程指向Bea Stollnitz 的解决方法),但我想知道是否有一种方法可以执行长时间运行的任务,显示 WorkingDialog,然后在完成任务后关闭对话框使用任务并行库完成了吗?任务并行库能否在不修改业务层代码的情况下达到预期的效果?

编辑 - 解决方法 1

感谢 Ben 的 cmets,如果您的长时间运行的任务与 UI 位于同一程序集中或使用 System.Windows 命名空间,则有一种解决方法。使用当前的应用程序调度程序,我可以毫无例外地将消息添加到我的列表中:

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!"))));
    }
}

我仍然很好奇这是否可以使用任务并行库实现,还是我找错了树?!在我的实际应用程序中,长时间运行的类位于单独的类库中。

编辑 2

我接受了以下 Ben 的解决方案,因为它解决了我最初的问题,即在不阻塞 UI 或引发异常的情况下运行长时间运行的任务。我很欣赏最初的问题是关于任务并行库的,所以如果您在阅读本文时认为它具有 TPL 解决方案,请原谅我。

【问题讨论】:

  • 您的 WorkingDialog 对这个故事至关重要吗?这听起来就像一个 Thread-to-GUI 问题。
  • 嗨 Henk,我添加了 WorkerDialog xaml,它只显示 WPF 工具包中的 BusyIndi​​cator。

标签: c# wpf multithreading c#-4.0 task-parallel-library


【解决方案1】:

您需要使用dispatcher 将新元素添加到您的日志列表中。

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

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

编辑

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!"));
  }
}

然后你在哪里创建你的业务类:

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

【讨论】:

  • 嗨@Ben - 当我尝试“不可调用的成员'System.Windows.Threading.DispatcherObject.Dispatcher'不能像方法一样使用时出现以下错误。”。任何想法我做错了什么?
  • @Ben 我想我已经通过获取 Dispatcher 然后调用 BeginInvoke 来解决这个问题。我会在上面更新我的帖子
  • @Ben - 我刚刚意识到这在我的应用程序中不起作用,因为长时间运行的任务位于不同的程序集(类库)中,因为它在System.Windows 命名空间。
  • @Gavin Sutherland 编辑了我的答案。
  • @Ben。谢谢......我会去试一试。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-01
  • 2012-08-13
  • 2022-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多