ShowDialog 中的 Catel async await 命令 - 死锁

Posted

技术标签:

【中文标题】ShowDialog 中的 Catel async await 命令 - 死锁【英文标题】:Catel async await command in ShowDialog - Deadlock 【发布时间】:2014-02-05 06:51:36 【问题描述】:

使用库Сatel 最新版本(3.8.1 beta)。

如何在对话窗口中使用 TAP 方法?

示例。 在主 ViewModel 中调用方法

private bool ShowDialogWindow()

    var typeFactory = TypeFactory.Default ;
    var vm = typeFactory.CreateInstanceWithParametersAndAutoCompletion<LoginWindowViewModel>();
    return _uiVisualizerService.ShowDialog(vm) ?? false;

在 LoginWindowViewModel 我有命令(也可以尝试 AsynchronousCommand),它被称为方法

public async Task<int> Test(string login, string password)

     var a = await Task<int>.Factory.StartNew(() =>
     
         using (var uow = new UnitOfWork<TSDbContext>())
         
             var userRep = uow.GetRepository<IUserRepository>();
             userRep.GetAll();
             return 5;
         
     );
     a++;
     return a;

我只有在关闭对话框窗口时才从等待的方法中得到结果。 锁出现在线

var uow = new UnitOfWork()

ConfigureAwait(false) - 无助于解决问题

当我删除 UnitOfWork - 方法有效

当我将方法代码更改为此 var d = TypeFactory.Default.CreateInstanceWithParameters(); 返回 5;

阻塞也复现就行TypeFactory...

根据服务,Catel 不允许出现在对话框中

【问题讨论】:

那么您的UnitOfWork 构造函数是做什么的? github.com/Catel/Catel/tree/develop/src/… 你有没有调试过它挂在哪里?我在那里看不到任何明显的问题...... 我不认为是锁。我认为这里的重点并不完全在于 unitofwork 作为访问配置或依赖解析的机制 你说“锁出现”-这表明它正在锁定(挂起)。那么,如果你在代码被锁定的情况下闯入代码,堆栈跟踪会是什么样子呢?我也不清楚这两个不同的代码 sn-ps 是如何相互关联的。 【参考方案1】:

注意:我编辑了这个答案,所以它包含了这个问题的答案。上一个答案包含一些提示,供主题启动者调查问题。

您在 MainViewModel 的 构造函数 中调用命令。请注意,我们从不建议您在构造函数中调用任何内容。为此,我们有 Initialize 方法。

原因是您使用 TypeFactory 构造了 MainViewModel(Catel 会为您做这件事)。然后在该线程中执行的同一(异步)命令中,您希望实例化一个 UnitOfWork,该 UnitOfWork 还希望通过 TypeFactory 实例化一个类型。这是在不同的线程上。 TypeFactory 仍处于锁定状态,因为您仍在构造 MainViewModel。

再次,Catel 在 ViewModelBase 上提供了 Initialize 方法,该方法在创建的外部 中调用,因此可以安全地在其中执行任何操作。请改用它。

【讨论】:

DbContext 创建工作,例如var a = new TSDbContext(); a.处置();返回 5; //工作!但是 UnitOfWork => DeadLock Last Line 输出窗口 [DEBUG] [Catel.IoC.ServiceLocator] 注册类型 'Catel.Data.IConnectionStringManager' 以键入 'Catel.Data.ConnectionStringManager' 我尝试了 Initialize 方法(在 View.Loaded 事件中调用)和命令,都可以使用以下代码正常工作: public async Task Test(string login, string密码) var a = await Task.Factory.StartNew(() => using (var uow = new UnitOfWork()) var userRep = uow.GetRepository(); userRep.GetAll (); 返回 5; );一个++;返回一个; 请在catelproject.com/support/issue-tracker的官方问题跟踪器中创建一个可重现应用程序(尽可能小)【参考方案2】:

我想我知道这里可能是什么问题。如果我对问题的理解是正确的,下面的代码会重现它:

public partial class MainWindow : Window

    class Model
    
        Model()  

        public Task<int> AsyncTask  get; private set; 

        public static Model Create()
        
            var model = new Model();
            Func<Task<int>> doTaskAsync = async () =>
            
                await Task.Delay(1);
                return 42;
            ;
            model.AsyncTask = doTaskAsync();
            return model;
        
    

    public MainWindow()
    
        InitializeComponent();
    

    private void Button_Click(object sender, RoutedEventArgs e)
    
        var textBlock = new TextBlock();
        var window = new Window  Content = textBlock ;

        window.Loaded += (sIgnore, eIgnore) =>
        
            // int result = ((Model)window.DataContext).AsyncTask.Result;
            // textBlock.Text = result.ToString();
        ;

        window.DataContext = Model.Create();
        window.ShowDialog();

        MessageBox.Show("Result: " + 
            ((Model)window.DataContext).AsyncTask.Result);
    

取消注释注释行,window.Loaded 事件处理程序内部将出现死锁,((Model)window.DataContext).AsyncTask.Result

这是因为window.Loaded 在调用ShowDialog 的Dispatcher 消息循环的同一迭代中同步触发AsyncTask 没有机会完成,因为 await Task.Delay(1) 之后的继续被安排在 UI 线程的 DispatcherSynchronizationContext 上。

相同的AsyncTask.Result 代码在ShowDialog 之后立即起作用。这是因为在关闭对话框之前,已经执行了更多消息循环的迭代(在对话框的新 Dispatcher 框架上)。

修复很简单:

window.Loaded += async (sIgnore, eIgnore) =>

    int result = await ((Model)window.DataContext).AsyncTask;
    textBlock.Text = result.ToString();
;

这将在对话框的 Dispatcher 框架上异步完成任务。

我不确定这与 OP 的情况有多接近,因为在上述情况下放置 await Task.Delay(1).ConfigureAwait(false) 也可以解决问题。不过,这是我根据 OP 的代码所能做出的猜测。

【讨论】:

问题是他说他是在 Command 中这样做的。我尝试使用 DbContext 和 UnitOfWork / Repository 重现此问题,但无法将其挂起(在 Initialize (View.Loaded) 和命令中)。 @GeertvanHorrik,该命令只是另一种事件。如果它是同步触发的,在ShowDialog 进入其模态消息循环之前的某个地方,效果将是相同的。 据我了解,他显示了 Login 窗口,并在 Login 窗口中执行了将调用 的命令Login 调用(这是异步的)。但他应该只是想出一个很好的可重现案例。 @GeertvanHorrik,我同意。到目前为止,我们只能做出猜测。 仅供参考:我们发现了问题。 TypeFactory 上存在多线程死锁。查看其他答案。

以上是关于ShowDialog 中的 Catel async await 命令 - 死锁的主要内容,如果未能解决你的问题,请参考以下文章

Catel 中的嵌套验证

CATEL按钮未运行UserControl中的执行操作

Flutter showDialog 如何刷新 setStatus

从 Catel WPF UserControl 中的 ResourceDictionary 中绑定

viewmodel + Catel 上的验证

DataWindowButton CanExecute 未触发,Catel 4.0