如果长时间运行的操作在ui线程上,则显示忙碌指示

时间:2014-12-05 16:51:08

标签: c# wpf multithreading

我有我的xaml:

        <Window x:Class="Views.ShellView"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
                xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
                Height="768" Width="1024" WindowStartupLocation="CenterScreen"
                Title="{Binding Path=DisplayName}">

            <xctk:BusyIndicator x:Name="BusyIndicator"  IsBusy="{Binding IsBusy, UpdateSourceTrigger=PropertyChanged}" >

                <TreeView Style="{StaticResource TableSchemaTreeViewStyle}" ItemContainerStyle="{StaticResource SchemaTreeViewStyle}" Margin="0,15,0,0" 
                                  x:Name="TreeViewSchema" 
                                  TreeViewItem.Expanded="TreeViewSchema_OnExpanded" 
                                  TreeViewItem.Selected="TreeViewSchema_OnSelected" 
                                  Grid.Row="2" 
                                  ItemsSource="{Binding CurrentProject.Tables, Converter={StaticResource TreeViewSortingConverter}, ConverterParameter=FullTableName}">
                </TreeView>

            </xctk:BusyIndicator>
        </Window>

假设我在代码隐藏中运行的任务很长,它在UI线程上执行(对treeview进行长时间过滤,它可以有超过1000个表,每个表中有超过100列)。

让我说我迭代并为每个项目设置tableTreeViewItem.Visibility = Visibility.Collapsed;

我想要的:通过在此操作之前将其设置为true来显示BusyIndi​​cator:

BusyIndicator.IsBusy = true;

问题:UI线程和绑定上的两个操作都无法按预期工作。我尝试了几件事:

BusyIndicator.IsBusy = true;

TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

Task.Factory.StartNew(() =>
{

    tableTreeViewItem.Visibility = Visibility.Collapsed;

}, CancellationToken.None, TaskCreationOptions.None, uiScheduler).ContinueWith(task => Dispatcher.Invoke(() =>
{
    BusyIndicator.IsBusy = false;
}));

使用调度员:

BusyIndicator.IsBusy = true;

//long-running UI task
tableTreeViewItem.Visibility = Visibility.Collapsed;

BusyIndicator.IsBusy = false;

但它不起作用,任何想法如何解决它?

PSS

我做了一些更新,我决定抓取所有数据并存储哪个树视图项应该可见或隐藏。

所以我有用于存储过滤方法

的表,可见性和可见列的类
 class TreeViewItemVisibilityTableContainer
{
    private TreeViewItem _treeViewItem;
    private TableModel _table;
    private Visibility _visibility;
    private List<ColumnModel> _visibleColumns;

    public TableModel Table
    {
        get { return _table; }
        set { _table = value; }
    }
    public TreeViewItem TreeViewItem
    {
        get { return _treeViewItem; }
        set { _treeViewItem = value; }
    }
    public Visibility Visibility
    {
        get { return _visibility; }
        set { _visibility = value; }
    }
    public List<ColumnModel> VisibleColumns
    {
        get { return _visibleColumns; }
        set { _visibleColumns = value; }
    }
}

现在我可以直接在UI线程上过滤所有这些工作人员:

 System.Action filterTreeViewItemsVisibility = () => Dispatcher.Invoke(() =>
            {
                foreach (var item in itemsToFilter)
                {
                    item.TreeViewItem.Visibility = item.Visibility;
                    var capturedItemForClosure = item;

                    if (item.Visibility == Visibility.Visible)
                    {
                        if (item.VisibleColumns.Any())
                        {
                            item.TreeViewItem.Items.Filter = item.TreeViewItem.Items.Filter =
                                treeViewItem =>
                                    capturedItemForClosure.VisibleColumns.Any(
                                        columnModel => columnModel.Equals(treeViewItem));
                        }
                        else
                        {
                            item.TreeViewItem.Visibility = Visibility.Collapsed;
                        }
                    }
                }
            });

            IoC.Get<IBusyIndicatorHelper>().PerformLongrunningAction(filterTreeViewItemsVisibility, IoC.Get<IShellViewModel>());

但它仍然超级慢

1 个答案:

答案 0 :(得分:4)

这是忙碌指标的解决方案。

显示解决方案的组件很少

  • BusyIndi​​cator 用户控制
  • AbortableBackgroundWorker
  • MainWindow 作为应用程序窗口

BusyIndi​​cator 用户控件

BusyIndi​​cator.xaml - 非常简单

<UserControl x:Class="BusyIndicatorExample.BusyInidicator"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300" Visibility="Collapsed">
<Grid Background="#BFFFFFFF" >
        <TextBlock Text="Loading data..." HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="#FF2C2C2C" FontSize="16" FontWeight="Bold" />
</Grid>
</UserControl>

<强> BusyIndi​​cator.xaml.cs

using System;
using System.Windows.Controls;

namespace BusyIndicatorExample
{
/// <summary>
/// Interaction logic for BusyInidcator.xaml
/// </summary>
public partial class BusyInidicator : UserControl
{
    public BusyInidicator()
    {
        InitializeComponent();
    }

显示指标的方法

    public void Start()
    {
        this.Dispatcher.Invoke(new Action(delegate()
        {

            this.Visibility = System.Windows.Visibility.Visible;
        }), System.Windows.Threading.DispatcherPriority.Normal);

    }

隐藏指标的方法

    public void Stop()
    {
        this.Dispatcher.Invoke(new Action(delegate()
        {

            this.Visibility = System.Windows.Visibility.Collapsed;
        }), System.Windows.Threading.DispatcherPriority.Normal);
    }
}
}
用于模拟非UI任务的

AbortableBackgroundWorker

using System;
using System.ComponentModel;
using System.Threading;

namespace BusyIndicatorExample
{

/// <summary>
/// Abortable background worker
/// </summary>
public class AbortableBackgroundWorker : BackgroundWorker
{
    //Internal Thread
    private Thread workerThread;

    protected override void OnDoWork(DoWorkEventArgs e)
    {
        try
        {
            base.OnDoWork(e);
        }
        catch (ThreadAbortException)
        {
            e.Cancel = true;        //We must set Cancel property to true! 
            Thread.ResetAbort();    //Prevents ThreadAbortException propagation 
        }
    }

    public void Abort()
    {
        if (workerThread != null)
        {
            workerThread.Abort();
            workerThread = null;
        }
    }
} 
}

最后,模拟过程的 MainWindow

<强> MainWindow.xaml

<Window x:Class="BusyIndicatorExample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:BusyIndicatorExample"
    Title="MainWindow" Height="350" Width="525">
<Grid>

    <Button Content="Start Data Loading" HorizontalAlignment="Left" Margin="63,42,0,0" VerticalAlignment="Top" Width="125" Height="28" Click="Button_Click"/>
   <TextBox HorizontalAlignment="Left" Height="23" Margin="63,87,0,0" TextWrapping="Wrap" Text="{Binding DataString}" VerticalAlignment="Top" Width="412"/>
    <local:BusyInidicator x:Name="busyIndicator" HorizontalAlignment="Left" Height="100" Margin="177,140,0,0" VerticalAlignment="Top" Width="300"/>
</Grid>
</Window>

MainWindow.xaml.cs - 这是应用程序代码

using System.ComponentModel;
using System.Windows;

namespace BusyIndicatorExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
    private AbortableBackgroundWorker _worker;

绑定到文本框的构造函数和公共属性

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    private string _dataString = "No Data";

    public string DataString
    {
        get { return _dataString; }
        set {
            if (_dataString != value)
            {
                _dataString = value;
                if (PropertyChanged != null)
                    PropertyChanged.Invoke(this, new PropertyChangedEventArgs("DataString"));
            }
        }
    }

按钮点击事件 - 初始化BackgroundWorker并启动它

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        if(_worker == null)
        {
            _worker = new AbortableBackgroundWorker();
            _worker.WorkerReportsProgress = true;
            _worker.WorkerSupportsCancellation = true;
            _worker.DoWork += _worker_DoWork;
            _worker.RunWorkerCompleted += _worker_RunWorkerCompleted;
        }

        if (!_worker.IsBusy)
            _worker.RunWorkerAsync();

    }

BackgroundWorker事件处理程序

RunWorkerCompleted更新数据字符串并隐藏指标。

    void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        DataString = "Data has been loaded";
        busyIndicator.Stop();
    }

DoWork显示指示符并进入睡眠线程5秒。

    void _worker_DoWork(object sender, DoWorkEventArgs e)
    {
        DataString = "No Data";
        busyIndicator.Start();
            System.Threading.Thread.Sleep(5000);
    }

    public event PropertyChangedEventHandler PropertyChanged;
}
}

希望这会有所帮助。根据需要修改代码以适应您的方案 可以下载完整的示例项目代码here