如何修复异步WMI select / PerformanceCounter上的UI死锁以获取远程计算机LastBootUpTime

时间:2019-01-24 09:05:51

标签: c# asynchronous wmi deadlock performancecounter

我正在创建用于控制公司网络上的远程工作站和服务器的Form Aplication,并且必须创建“等待远程计算机重新启动”功能。这个功能还可以,但是我需要它异步运行,这是我的问题... 该功能首先检查联机/脱机状态以确定重新启动,然后检查远程计算机的新LastBootUpTime值,以确保它确实重新启动,而不仅仅是网络问题。 当我异步运行此检查时,如果使用.Get()方法,则ManagementObjectSearcher会触发死锁。当我改用PerformanceCounter时遇到同样的问题。

有3个主要对象: 1)表格课 2)关系类(由Form拥有) 3)RestartChecker类(由Relation拥有)

当RestartChecker获取进行重启的信息时,将此信息谷事件发送给Relation。关系使用它自己的事件将其发送到UI上的“表单”和“表单更改”图标。

这是我来自RestartChecker的代码(重要部分):

此方法在Relation类中,它启动RestartChecker。从Form类中调用此Relation方法。

    public void StartRestartMonitoring()
    {
        restartChecker = new RestartChecker(machine.Name, machine.OperatingSystem.lastBootUpTime.Value, wmiSuccess);

        //WasRestarted property calls event on value change to true. That event change icons on Form
        restartChecker.RestartWasMade += new Action(() => { WasRestarted = true; }); 

        restartChecker.Start();
    }

此方法启动检查重启功能

Task checker;
CancellationTokenSource tokenSource;   

    public void Start()
    {
        tokenSource = new CancellationTokenSource();
        CancellationToken token = tokenSource.Token;

        checker = CheckActionAsync(token);
        running = true;
    }

这是更重要的部分=> Task方法应该异步运行

    private async Task CheckActionAsync(CancellationToken ct)
    {
        bool isOnline = await RemoteTask.PingAsync(target, PING_TIMEOUT_SECONDS);
        int onlineState = (isOnline) ? 0 : 1;

        try
        {
            lastKnownBootUpTime = (isOnline) ? (GetLastBootUpTime(target, useWMI) ?? lastKnownBootUpTime) : lastKnownBootUpTime;
        }
        catch (Exception ex)
        {
            //Logs to File
            EventNotifier.Log(ex,....);
        }

        //This part looks OK...
            while (onlineState < 2)
            {
                if (ct.IsCancellationRequested) { return; }

                bool actualOnlineState = await RemoteTask.PingAsync(target, PING_TIMEOUT_SECONDS);
                onlineState += (actualOnlineState == isOnline) ? 0 : 1;

                await Task.Delay(CHECK_INTERVAL);
            }

        while (!ct.IsCancellationRequested)
        {
            if (ct.IsCancellationRequested) { return; }

            //Here, until I get properly value for LastBootUpTime of remote machine, I'm still trying again and again (beacause first try is cannot be OK => machine is Online, but services for WMI is not ready yet, so there is exception on first try)
            while (newBootUpTime == null)
            {
                try
                {
                    newBootUpTime = GetLastBootUpTime(target, useWMI);
                }
                catch (Exception ex)
                {
                    //Some reactions to exception including logging to File
                }

                await Task.Delay(INTERVAL);
            }

            //This part looks ok too..
            newBootUpTime = newBootUpTime.Value.AddTicks(-newBootUpTime.Value.Ticks % TimeSpan.TicksPerSecond);
            lastKnownBootUpTime = lastKnownBootUpTime.Value.AddTicks(-lastKnownBootUpTime.Value.Ticks % TimeSpan.TicksPerSecond);

            if (newBootUpTime.Value > lastKnownBootUpTime.Value)
            {
                RestartWasMade?.Invoke();
                return;
            }

            await Task.Delay(CHECK_INTERVAL);
        }
    }

GetLastBoostUpTime方法

    private static DateTime? GetLastBootUpTime(string target, bool useWMI)
    {
        DateTime? lastBootUpTime = null;

        if (useWMI)
        {
            //wmiBootUpTime is SelectQuery
            string dateInString = RemoteTask.SelectStringsFromWMI(wmiBootUpTime, new ManagementScope(string.Format("\\\\{0}\\root\\cimv2", target))).First()[wmiBootUpTime.SelectedProperties[0].ToString()];
            lastBootUpTime = (string.IsNullOrEmpty(dateInString)) ? null : (DateTime?)ManagementDateTimeConverter.ToDateTime(dateInString);
        }
        else
        {
            TimeSpan? osRunningTime = RemoteTask.GetUpTime(target);
            lastBootUpTime = (osRunningTime == null) ? null : (DateTime?)DateTime.Now.Subtract(osRunningTime.Value);
        }

        return lastBootUpTime;
    }

用于获取数据的WMI方法:

    public static List<Dictionary<string, string>> SelectStringsFromWMI(SelectQuery select, ManagementScope wmiScope)
    {
        List<Dictionary<string, string>> result = new List<Dictionary<string, string>>();
        using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(wmiScope, select))
        {
            //This line is deadlock-maker... Because remote machine  services is not ready yet, searcher.Get() is trying
            //until reach it's timeout (by default it is 30s) and that's my deadlock. For the time of running searcher.Get()
            //there is 30s deadlock. Where is the mistake I've made? I supposed that this can not confront my UI thread
            using (ManagementObjectCollection objectCollection = searcher.Get())
            {
                foreach (ManagementObject managementObject in objectCollection)
                {
                    result.Add(new Dictionary<string, string>());
                    foreach (PropertyData property in managementObject.Properties)
                    {
                        result.Last().Add(property.Name, property.Value?.ToString());
                    }
                }

                return result;
            }
        }
    }

用于获取数据的PerformanceCounte方法:

    public static TimeSpan? GetUpTime(string remoteMachine = null)
    {
        try
        {
            using (PerformanceCounter upTime = (string.IsNullOrWhiteSpace(remoteMachine))
                ? new PerformanceCounter("System", "System Up Time")
                : new PerformanceCounter("System", "System Up Time", null, remoteMachine))
            {
                upTime.NextValue();
                return TimeSpan.FromSeconds(upTime.NextValue());
            }
        }
        catch
        {
            return null;
        }
    }

异步ping方法

    public async static Task<bool> PingAsync(string target, int pingTimeOut)
    {
        bool result = false;
        Exception error = null;

        using (Ping pinger = new Ping())
        {
            try
            {
                PingReply replay = await pinger.SendPingAsync(target, pingTimeOut * 1000);
                result = (replay.Status == IPStatus.Success) ? true : false;
            }
            catch (Exception ex)
            {
                error = ex;
            }
        }

        if (error != null) { throw error; }

        return result;
    }

1 个答案:

答案 0 :(得分:2)

我在这里看不到死锁,但我看到您通过同步调用阻止了异步方法

newBootUpTime = GetLastBootUpTime(target, useWMI);

您可以在异步线程中异步调用此方法,或者使GetLastBootUpTime方法异步

newBootUpTime = await Task.Run(() => GetLastBootUpTime(target, useWMI));

您还应该使用上述方法,从异步方法中删除所有其他同步阻止调用。

仅当您致电

时,死锁才可能导致

checker.Wait();在您创建Task checker的线程中的某个位置(可能是UI线程)

您正在这样做吗?

您还可以在此处了解什么是死锁以及如何避免死锁

https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html