调用异步方法后,进程似乎没有回到 WPF 应用程序中的主线程

Posted

技术标签:

【中文标题】调用异步方法后,进程似乎没有回到 WPF 应用程序中的主线程【英文标题】:After calling an async method the process don't seem to go back to main thread in WPF app 【发布时间】:2021-04-23 22:56:52 【问题描述】:

我对 WPF 和线程都是新手,这部分是基于我没有编写的代码,所以我什至无法提出正确的问题或不知道去哪里寻找。

我有一个带有切换按钮的应用程序,可以停止和启动一些服务。

当您启动应用程序时,服务正在运行。然后,您可以单击按钮停止它们。 在等待服务停止(或启动)时,该按钮被禁用。 当所有服务都从一种状态转换到另一种状态时,按钮上的文本会发生变化,并且应该启用按钮,除非它没有,文本正在更改,但按钮不会启用。但是,如果我单击 GUI 上的任意位置,按钮就会启用。 这让我认为点击以某种方式(重新)激活了一个线程。

代码注释

在构造函数中,我参考CanServicesExecute()方法来判断按钮是否启用。 所以当程序运行时,这个方法一直被系统击中。

检查服务是否正在运行的部分由单独的线程完成,以便不阻止 GUI 的其余部分,而仅禁用一个按钮。它每 5 秒检查一次。

在这段代码中,我用简单的等待代替了实际检查服务是否已终止或停止,但我的问题仍然存在:在 Thread.Sleep 之后,正在设置 Servicesstate,并且按钮正在获取另一个文本,但是CanServicesExecute() 不再被击中。直到我单击 GUI 中的某个位置,然后它又被不断地击中。

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Contracts.Annotations;
using Contracts.BusinessObjects;
using eDeployment.Properties;
using eTray.Common;
using eTray.Server;
using eTray.Types.Server;
using Infrastructure.Commands;
using Settings = eDeployment.Properties.Settings;

namespace eDeployment.ViewModel

    public class DeploymentViewModel : INotifyPropertyChanged
     

        enum ServicesState
        
            Running = 0,
            Terminated = 1,
            Transitioning = 2,
            Unfinished = 3
        

        
        private ServicesState IsServicesRunning  get; set; 


        public DeploymentViewModel()
        
            StopStartServicesCommand = new RelayCommand<object>(_ => StopStartServices(), _ => CanServicesExecute());
        

        private void StopStartServices()
        
            if (IsServicesRunning == ServicesState.Running)
                StopServicesCommand();
            else
            
                StartServicesCommand();
            
        

        private async void StopServicesCommand()
        
            SetServiceState(ServicesState.Transitioning, Resources.TryStopServices);
            //CheckServicesX(false);
            CheckServicesProcessX(true);
        

        private async void StartServicesCommand()
         
            SetServiceState(ServicesState.Transitioning, Resources.TryStartServices);
            LogWrite(Resources.TryStartServices, false);
            //CheckServicesX(true);
            CheckServicesProcessX(true);
        

        private async void CheckServicesProcessX(bool starting)
        
           await Task.Run(() => CheckServicesX(starting))
               .ContinueWith(t =>  , TaskScheduler.FromCurrentSynchronizationContext());

        


        private  void CheckServicesX(bool starting)
        
            Thread.Sleep(3000); 
            
                if (!starting)
                
                    SetServiceState(ServicesState.Terminated, Resources.ServicesTerminated);
                    LogWrite(Resources.ServicesTerminated, false);
                    
                
                else
                
                     SetServiceState(ServicesState.Running, Resources.ServicesRunning);
                    LogWrite(Resources.ServicesRunning, false);
                   
                
        
        
        private async void CheckServicesY(bool starting)
        
            
             await Task.Delay(3000); 
            
                if (!starting)
                

                    SetServiceState(ServicesState.Terminated, Resources.ServicesTerminated);
                    LogWrite(Resources.ServicesTerminated, false);
                    
                
                else
                
                     SetServiceState(ServicesState.Running, Resources.ServicesRunning);
                    LogWrite(Resources.ServicesRunning, false);
                   
                
        

        private void SetServiceState(ServicesState servicestate, string message)
        
            switch (servicestate)
            
                case ServicesState.Running:
                    IsServicesRunning = ServicesState.Running;
                    ColorIndicator = "Green";
                    ServicesLabel = Resources.LabeL_StopServices;

                    break;
                case ServicesState.Transitioning:
                    IsServicesRunning = ServicesState.Transitioning;
                    ColorIndicator = "Orange";

                    break;
                case ServicesState.Terminated:
                    IsServicesRunning = ServicesState.Terminated;
                    ServicesLabel = Resources.LabeL_StartServices;
                    ColorIndicator = "Red";

                    break;
                case ServicesState.Unfinished:
                    IsServicesRunning = ServicesState.Terminated;
                    ColorIndicator = "Orange";

                    break;
            

            ServiceStatusText = message;
        


        private bool CanServicesExecute()
        
            if (IsServicesRunning == ServicesState.Transitioning)
                return false;
            return true;
        
    

【问题讨论】:

如果 som UI 组件未在 WPF 中更新,最常见的原因是未引发 OnPropertyChanged 事件,或使用不正确的参数引发。 你能显示 xaml 吗?什么是 IsEnabled 绑定? 正在引发 OnPropertyChanged。如果我同步运行它,它应该可以正常工作,但是 GUI 在整个持续时间内都被锁定。看起来这不是返回ui线程,而是FromCurrentSynchronizationContext不是ui线程吗?等待 Task.Run(() => CheckServicesX(starting)) .ContinueWith(t => , TaskScheduler.FromCurrentSynchronizationContext()) 【参考方案1】:

您需要确保在您想要刷新绑定到StopStartServicesCommandButton 的状态时调用CanServicesExecute。您可以通过引发命令的 CanExecuteChanged 事件来做到这一点。

如何引发事件取决于您正在使用的RelayCommand&lt;T&gt; 类的实现。它应该有一个引发事件的公共方法:

StopStartServicesCommand.RaiseCanExecuteChanged();

CheckServicesX 中调用此函数,或者在您想根据当前状态启用或禁用Button 时调用此函数。

【讨论】:

所以它没有引发事件的方法,但我用谷歌搜索该事件基本上只是调用 CommandManager.InvalidateRequerySuggested();所以我打电话给它,它奏效了。谢谢

以上是关于调用异步方法后,进程似乎没有回到 WPF 应用程序中的主线程的主要内容,如果未能解决你的问题,请参考以下文章

关于在 C# (WPF) 中对 COM 对象进行异步调用的问题

异步回调函数-创建进程的三种方式

同步异步,阻塞非阻塞,并发并行

如何根据进程的控制台输出更新WPF控件?

同步与异步的概念

如何在双向绑定组合框(WPF)上调用异步操作