混合使用Task和Dispatcher会停止任务

时间:2014-05-02 08:55:50

标签: c# wpf asynchronous task dispatcher

解释

我在WPF中创建自己的搜索控件。此控件是UserControl,其中包含具有搜索参数的区域(例如:搜索特定ID,名称,...)和显示结果的GridView

在我的控件中,我有dependency property类型ICommand,我绑定命令以执行我的搜索查询。

public static readonly DependencyProperty SearchCommandProperty =
            DependencyProperty.Register("SearchCommand", typeof(ICommand), typeof(SearchControl));

在某个窗口中使用我的控件:

 <customControls:SearchControl SearchCommand="{Binding SearchItemsCommand}"
                           SearchResult="{Binding SearchResult}" />
  • SearchItemsCommand是我的ViewModel中的Command,我可以在其中找到我的搜索查询。 在此命令中,您可以找到我的查询以检索结果。
  • SearchResult是我的ICollection,其中包含搜索查询的结果。

命令代码

视图模型

 private DelegateCommand searchItemsCommand;
        public DelegateCommand SearchItemsCommand
        {
            get
            {
                if (this.searchItemsCommand== null)
                    this.searchItemsCommand= new DelegateCommand(this.SearchItemsCommandExecuted);

                return this.searchItemsCommand;
            }
        }

private ICollection<VoucherOverviewModel> voucherResults;
private void SearchItemsCommandExecuted()
        {
            using (DbContext context = new DbContext())
            {
                var query = (from v in context.Vouchers
                             join vt in context.VoucherTransactions on new
                                                                       {
                                                                           voucherID = v.VoucherID,
                                                                           type = VoucherTransactionType.Out
                                                                       } equals new
                                                                               {
                                                                                   voucherID = vt.VoucherID,
                                                                                   type = vt.Type
                                                                               }
                             join vtype in context.VoucherTypes on v.VoucherTypeID equals vtype.VoucherTypeID
                             join c in context.Customers on vt.CustomerID equals c.CustomerID
                             join pos in context.PointOfSales on v.PointOfSaleID equals pos.PointOfSaleID
                             select new VoucherOverviewModel()
                                    {
                                        PointOfSaleID = v.PointOfSaleID,
                                        PointOfSaleName = pos.Name,
                                        VoucherID = v.VoucherID,
                                        VoucherCode = v.Code,
                                        VoucherTypeID = v.VoucherTypeID,
                                        VoucherTypeDescription = vtype.Code,
                                        CustomerID = c.CustomerID,
                                        CustomerName = c.Name,
                                        Value = vt.Value,
                                        UsedValue = context.VoucherTransactions
                                                           .Where(x => x.VoucherID == v.VoucherID &&
                                                               x.Type == VoucherTransactionType.In)
                                                           .Sum(x => x.Value),
                                        CreateDate = vt.Date,
                                        ValidFrom = v.ValidFrom,
                                        ValidUntil = v.ValidUntil,
                                        ParentVoucherID = v.ParentVoucherID,
                                        Comment = v.Comment,
                                    });

                foreach (ISearchParameter searchParameter in this.SearchParameters)
                {
                    if (!searchParameter.Value.IsNullOrDefault())
                    {
                        switch ((FilterVoucherParameterKey)searchParameter.Key)
                        {
                            case FilterVoucherParameterKey.CustomerID:
                                query = query.Where(x => x.CustomerID == (int)searchParameter.Value);
                                break;
                            case FilterVoucherParameterKey.VoucherID:
                                query = query.Where(x => x.VoucherCode.Contains((string)searchParameter.Value));
                                break;
                            case FilterVoucherParameterKey.PointOfSale:
                                query = query.Where(x => x.PointOfSaleID == (byte)searchParameter.Value);
                                break;
                            case FilterVoucherParameterKey.Type:
                                query = query.Where(x => x.VoucherTypeID == (byte)searchParameter.Value);
                                break;
                        }
                    }
                }

            this.voucherResults = query.ToList();
            }
        }

自定义控件

    public static readonly DependencyProperty SearchCommandProperty =
        DependencyProperty.Register("SearchCommand", typeof(ICommand), typeof(SearchControl));

 public ICommand SearchCommand
        {
            get
            {
                return (ICommand)this.GetValue(SearchCommandProperty);
            }
            set
            {
                this.SetValue(SearchCommandProperty, value);
            }
        }

这是我的依赖项属性,因此我可以将SearchItemsCommand绑定到我的自定义控件。 然后我有另一个ICommand来执行binded命令并在我的自定义控件中显示加载元素。 单击按钮时将执行此LocalSearchCommand

 private DelegateCommand localSearchCommand;
 public DelegateCommand LocalSearchCommand
    {
        get
        {
            if (this.localSearchCommand == null)
                this.localSearchCommand = new DelegateCommand(this.LocalSearchCommandExecuted);

            return this.localSearchCommand;
        }
    }

    private void LocalSearchCommandExecuted()
    {
loadingElement.Visible = true;
Task.Factory.StartNew(() =>
                       {
                           this.Dispatcher.Invoke((Action)(() => this.SearchCommand.Execute(null)));
                       })
                       .ContinueWith(t =>
                       {
                           if (t.IsCompleted)
                           {
                               t.Dispose();
                           }
                       });
    }

问题

我想在执行查询以与用户交互时显示 Loading元素。要显示此元素,我必须将其设置为visible。 现在的问题是,当我将其设置为可见并想要执行搜索命令时,我的整个UI都会冻结。从数据库中获取结果并在GridView中生成结果后,只有这样,它才会显示我的加载元素。我明白为什么会发生这种情况,我试图用Task来解决它。

loadingElement.Visible = true;
Task.Factory.StartNew(() =>
                       {
                           this.Dispatcher.Invoke((Action)(() => this.SearchCommand.Execute(null)));
                       })
                       .ContinueWith(t =>
                       {
                           if (t.IsCompleted)
                           {
                               t.Dispose();
                           }
                       });

我必须使用Dispatcher中的Task来执行SearchCommand,因为它由UI线程拥有。 但由于使用了Dispatcher类,我遇到了和以前一样的问题。我的加载元素仅在查询已执行时显示,因为Dispatcher在UI线程上执行搜索命令。 如果不使用Dispatcher类,它会给我以下错误:

The calling thread cannot access this object because a different thread owns it.

我收到了这个错误:

return (ICommand)this.GetValue(SearchCommandProperty);

即使使用空SearchItemsCommandExecuted方法,也会发生错误。

我已尝试过的内容

  • 我尝试将TaskScheduler的{​​{1}}设置为

    Task

  • 我使用了很多TaskScheduler.FromCurrentSynchronizationContext()BeginInvoke的组合。

  • 我尝试在Invoke中设置加载元素Visibility

但以上都没有奏效。

如何解决我的问题,以便在执行查询时显示加载元素。我错过了一些明显的东西吗?

提前致谢!

Loetn

3 个答案:

答案 0 :(得分:1)

问题是您正在使用ThreadPool线程创建新的Task,但使用Dispatcher.Invoke,它在UI线程上运行您的命令,因此您的UI冻结。

您需要将SearchCommand工作卸载到后台线程,然后在UI线程上使用Continuation 更新您的UI(不要尝试在SearchCommand内更新您的UI) :

然后,它显示我的加载元素。我明白为什么会发生这种情况,我试图用任务来解决它。

loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
    return this.SearchCommand.Execute(null);
})
.ContinueWith(t =>
{
    MyUIElement = t.Result; // Update your UI here.
}, TaskScheduler.FromCurrentSynchronizationContext());

答案 1 :(得分:0)

编辑:没有捕获第一个命令与第二个命令的绑定。因此,以下将无法正常工作。调查它......

编辑2:我假设你想从你的viewmodel开始后台操作。目前我想不出另一种方法,而不是使你的loadingItem.Visible属性成为依赖属性,将后台操作移动到你的viewmodel,从那里分配一个绑定到loadingItem.Visible的属性,并从你的中移除异步的东西用户控件。

您想在后台线程上开始查询并将结果分配给您的ui线程:

    private void LocalSearchCommandExecuted(object obj)
    {
        //can also be your loadingItem. 
        VisibleElement.Visibility = Visibility.Collapsed;
        //get the ui context
        var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
        Task.Factory.StartNew(() =>
        {
            //do your query on the background thread
            LongRunningOperation();
        })
                  //this happens on the ui thread because of the second parameter scheduler 
                               .ContinueWith(t =>
                               {

                                   if (t.IsCompleted)
                                   {
                                       VisibleElement.Visibility = Visibility.Visible;
                                       //assign the result from the LongRunningOperation to your ui list
                                       _list = new List<string>(_tempList);
                                       //if you need to...
                                       RaisePropertyChanged("SearchResults");
                                   }
                               }, scheduler );

    }

    private void LongRunningOperation()
    {
        //assign your result to a temporary collection
        //if you do not do that you will get an exception: An ItemsControl is inconsistent with its items source
        _tempList = new List<string>();
        for (int i = 0; i < 100; i++)
        {
            _tempList.Add("Test" + i);
            Thread.Sleep(10);
        }
    }

答案 2 :(得分:0)

我在this blog的帮助下解决了我的问题。

我必须要修改getter的{​​{1}},以便它使用Dependency property SearchCommand

Dispatcher

这是我的public ICommand SearchCommand { get { return (ICommand)this.Dispatcher.Invoke( System.Windows.Threading.DispatcherPriority.Background, (DispatcherOperationCallback)delegate { return this.GetValue(SearchCommandProperty); }, SearchCommandProperty); // Instead of this // return this.GetValue(SearchCommandProperty); } set { this.SetValue(SearchCommandProperty, value); } }

Command method

感谢您的帮助!