【问题标题】:WPF: Execute some code asynchronously in .NET 3.5WPF:在 .NET 3.5 中异步执行一些代码
【发布时间】:2018-08-30 17:29:49
【问题描述】:

我有一个 MVVM WPF 应用程序。我有一个窗口,比方说“LvWindow”,它有一个从来自数据库的数据加载的列表视图。从主窗口“MainWindow”,我有一个菜单,其中有一些选项。当我选择访问“LvWindow”的选项时,它是打开的。然后从 ViewModel 中,在构造函数中调用一个数据库,从该数据库中请求一些数据,然后将这些数据加载到列表视图中。

我的目标是使进程从数据库中请求数据,然后将其异步加载到列表视图中。我希望这样做是为了不阻止整个应用程序,我的意思是,在加载此窗口期间,用户可以转到主窗口菜单并选择打开另一种类型的窗口。窗口在选项卡中打开。

在从数据库请求数据并加载到窗口“LvWindow”中的列表视图的过程中,我会在其上显示“正在加载”的飞溅(实际上这是一个将 zindex 设置为更高数字的矩形,以避免用户可以与 listview 交互,直到它完全加载)。当列表视图加载来自数据库的数据时,此启动画面将关闭。

因此,为了使流程异步,我知道在 winforms 中可以通过使用 beginInvoke、endInvoke 和回调方法使用委托来完成,请参阅 here

另外,另一种可能性是使用后台工作人员,例如发布的here

那么在 WPF 中哪一种是最好的方法呢?使用委托作为 winform 或后台工作人员?

尝试 #1: 我已经尝试过这样的 XANIMAX 解决方案:

public class TestViewModel : BaseViewModel
{
    private static Dispatcher _dispatcher;
    public ObservableCollection<UserData> lstUsers

    public ObservableCollection<UserData> LstUsers
    {
        get
        {
            return this.lstUsers;
        }

        private set
        {
            this.lstUsers= value;
            OnPropertyChanged("LstUsers");
        }
    }

    public TestViewModel()
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
        {
            var result = getDataFromDatabase();
            UIThread((p) => LstUsers = result);
        }));
    }

    ObservableCollection<UserData> getDataFromDatabase()
    {            
        return this.RequestDataToDatabase();
    }

    static void UIThread(Action<object> a)
    {
        if(_dispatcher == null) _dispatcher = Dispatcher.CurrentDispatcher;
        //this is to make sure that the event is raised on the correct Thread
        _dispatcher.Invoke(a); <---- HERE EXCEPTION IS THROWN
    }
}

但在 _dispatcher.Invoke(a) 行中抛出异常:

TargetParameterCountException: the parameter count mismatch

UserData 是我的数据模型,它是一个具有一些公共属性的类。比如:

public class UserData
{
   public string ID{ get; set; }
   public string Name { get; set; }
   public string Surname { get; set; }

   // Other properties
}

所以问题是对数据库的调用返回“RequestDataToDatabase”正在返回 UserData (ObservableCollection) 的集合,因此引发了异常。

不知道怎么解决。请问你能帮帮我吗?

最终解决方案

正如 XAMIMAX 在 cmets 中所说:

  • 将签名从 static void UIThread(Action a) 更改为 static void UIThread(Action a)
  • 修改 UIThread((p) => LstUsers = 结果);通过 UIThread(() => LstUsers = 结果);

【问题讨论】:

  • 你说的是 MvvM,但你从来没有提到过 ViewModel。如果这是 MvvM,尽管缺少视图模型,您可以使用 Async Binding
  • "...在其构造函数中,我调用了一个数据库",这不是 MVVM 方式。
  • @Filburt 抱歉,我的意思是,viewmodel 的构造函数。
  • 将签名从 static void UIThread(Action&lt;object&gt; a) 更改为 static void UIThread(Action a)。这将允许方法按照它期望的参数执行。
  • @XAMlMAX 最好发布另一个问题,我将在那里详细解释。我会回来并在 cmets 中发布链接。

标签: c# wpf listview asynchronous delegates


【解决方案1】:

由于您不能在 C# 7.0 的构造函数中等待异步方法(但异步 Main 将在 7.1 中出现),您可以将异步函数调用提取到 ViewModel 中的单独函数,并在 View 的代码隐藏中同步调用它构造函数,在创建 ViewModel 并将其分配给 View 的 DataContext 之后:

public MainWindow()
{
    this.vm = new MyViewModel();
    this.DataContext = this.vm;
    this.InitializeComponent();
    this.vm.AsychronousFunctionToCallDatabase();
}

正如 XAMIMAX 所说,您希望实现一个 ViewModel 来处理您的视图和模型之间的业务逻辑。然后,如果您的 ViewModel 实现了 INotifyPropertyChanged 并且您在 XAML 中设置了与 ViewModel 中的属性的绑定 - 那么显示将在数据库调用后刷新而不会阻塞 UI 线程。请注意,如果您从数据库调用中填充了任何集合,那么它们应该是 ObservableCollection 类型。

但是正如 Kundan 所说,在您的 AsychronousFunctionToCallDatabase() 函数中,您应该在调用数据库的行中包含等待语句或创建任务 - 这会将控制权返回给调用函数(在这种情况下,返回给 MainWindow 构造函数)。

【讨论】:

  • 在 Net 3.5 中不支持 Task。
【解决方案2】:

这是您可能的解决方案之一。
在您的视图模型中,您将拥有如下内容:

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;

namespace VM
{
    public class TestViewModel : BaseViewModel
    {
        private static Dispatcher _dispatcher;
        List<object> ListToDisplay { get; set; }//INPC omitted for brevity
        public TestViewModel()
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
            {
                var result = getDataFromDatabase();
                UIThread(() => ListToDisplay = result);
            }));
        }

        List<object> getDataFromDatabase()
        {
            //your logic here
            return new List<object>();
        }

        static void UIThread(Action a)
        {
            if(_dispatcher == null) _dispatcher = Dispatcher.CurrentDispatcher;
            //this is to make sure that the event is raised on the correct Thread
            _dispatcher.Invoke(a);
        }
    }
}

【讨论】:

  • 我已经实现了你的解决方案,但我得到一个异常:TargetParameterCountException:参数计数不匹配。看到我的帖子更新了。问题是从数据库返回信息的方法是返回用户数据的集合。你能告诉我如何解决这个问题吗?
【解决方案3】:
猜你喜欢
  • 1970-01-01
  • 2014-07-06
  • 2017-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-25
  • 2019-01-28
相关资源
最近更新 更多