简体   繁体   English

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

[英]How to fix UI deadlock on async WMI select/PerformanceCounter for getting remote machine LastBootUpTime

I'm just creating Form aplication for controling remote workstations and servers on our company network and I have to create "Wait on restart of remote machine" function. 我正在创建用于控制公司网络上的远程工作站和服务器的Form Aplication,并且必须创建“等待远程计算机重新启动”功能。 This function is OK, but I need it to run asynchoniously and there is my problem... The function check firstly online/offline status to determine restart and after that it is check new LastBootUpTime value of remote machine to be sure it was really restart and not only network problem. 此功能还可以,但是我需要它异步运行,这是我的问题...该功能首先检查联机/脱机状态以确定重新启动,然后检查远程计算机的新LastBootUpTime值以确保它确实重新启动不仅是网络问题。 When I run this check asynchroniously, ManagementObjectSearcher trigger deadlock, when using it's .Get() method. 当我异步运行此检查时,如果使用.Get()方法,则ManagementObjectSearcher会触发死锁。 The same problem I have when I use PerformanceCounter instead. 当我改用PerformanceCounter时遇到同样的问题。

There is 3 main objects for this: 1) Form class 2) Relation class (owned by Form) 3) RestartChecker class (owned by Relation) 有3个主要对象:1)表单类2)关系类(由Form拥有)3)RestartChecker类(由Relation拥有)

When RestartChecker get info that restart was made, sends this info trough event to Relation. 当RestartChecker获取进行重启的信息时,将此信息通过事件发送给Relation。 Relation use it's own event to sends it to Form and Form change icon on UI. 关系使用它自己的事件将其发送到UI上的“表单”和“表单更改”图标。

Here is my code (important parts) from RestartChecker: 这是我来自RestartChecker的代码(重要部分):

This method is in Relation class and it's starts the RestartChecker. 此方法在Relation类中,它启动RestartChecker。 This Relation method is called from Form class. 从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();
    }

This method starts the check for restart function 此方法启动检查重启功能

Task checker;
CancellationTokenSource tokenSource;   

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

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

This is the more important part => Task method which should run asynchoniously 这是更重要的部分=> 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 method 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 method used for getting the data: 用于获取数据的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 method used for getting the data: 用于获取数据的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;
        }
    }

Asynchronious ping method 异步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;
    }

I don't see deadlock here, but I see you block async method with sync call 我在这里看不到死锁,但我看到您使用同步调用阻止了异步方法

newBootUpTime = GetLastBootUpTime(target, useWMI);

you can should call this in separate thread asynchronously I think or make GetLastBootUpTime method async 您可以异步地在单独的线程中调用此函数,或者使GetLastBootUpTime方法异步

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

You also should remove all other sync blocking calls from your async methods using way above.. 您还应该使用上述方法,从异步方法中删除所有其他同步阻止调用。

Deadlock may cause only if you call 仅当您致电时,死锁才可能导致

checker.Wait(); somewhere in thread in which you created Task checker (UI thread probably) 在其中创建Task checker线程中的某个位置(可能是UI线程)

Are you doing this? 你在做这个吗?

Also you can read about what is deadlock and how to avoid it here 您也可以在此处了解什么是死锁以及如何避免死锁

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM