Как исправить тупик пользовательского интерфейса в асинхронном WMI select / PerformanceCounter для получения удаленного компьютера LastBootUpTime - PullRequest
0 голосов
/ 24 января 2019

Я просто создаю приложение Form для управления удаленными рабочими станциями и серверами в сети нашей компании, и мне нужно создать функцию «Ожидание перезагрузки удаленного компьютера».Эта функция в порядке, но мне нужно, чтобы она работала асинхронно, и есть моя проблема ... Функция сначала проверяет состояние онлайн / офлайн, чтобы определить перезапуск, а затем проверяет новое значение LastBootUpTime удаленного компьютера, чтобы убедиться, что он действительно перезапущен.и не только проблема сети.Когда я запускаю эту проверку асинхронно, ManagementObjectSearcher вызывает взаимоблокировку при использовании его метода .Get ().Та же проблема у меня возникает, когда я вместо этого использую PerformanceCounter.

Для этого есть 3 основных объекта: 1) Класс формы 2) Класс отношений (принадлежит Форме) 3) Класс RestartChecker (принадлежит Владельцам)

Когда RestartChecker получает информацию о перезапуске, отправляет это информационное событие в Relation.Relation использует свое собственное событие, чтобы отправить его на значок формы и изменения формы в пользовательском интерфейсе.

Вот мой код (важные части) из RestartChecker:

Этот метод находится в классе Relation и запускаетRestartChecker.Этот метод Relation вызывается из класса Form.

    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;
    }

Это более важная часть => Метод задачи, который должен выполняться асинхронно

    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;
        }
    }

метод асинхронного пинга

    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 голосов
/ 24 января 2019

Я не вижу здесь тупика, но я вижу, как вы блокируете асинхронный метод с синхронизирующим вызовом

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

...