如何修复异步 WMI 选择/PerformanceCounter 上的 UI 死锁以获取远程计算机 LastBootUpTime
Posted
技术标签:
【中文标题】如何修复异步 WMI 选择/PerformanceCounter 上的 UI 死锁以获取远程计算机 LastBootUpTime【英文标题】:How to fix UI deadlock on async WMI select/PerformanceCounter for getting remote machine LastBootUpTime 【发布时间】:2019-01-24 09:05:51 【问题描述】:我只是创建表单应用程序来控制我们公司网络上的远程工作站和服务器,我必须创建“等待远程机器重启”功能。这个功能没问题,但我需要它异步运行,这是我的问题...... 该功能首先检查在线/离线状态以确定重新启动,然后检查远程机器的新 LastBootUpTime 值以确保它真的重新启动,而不仅仅是网络问题。 当我异步运行此检查时,ManagementObjectSearcher 在使用它的 .Get() 方法时会触发死锁。当我改用 PerformanceCounter 时,我遇到了同样的问题。
有 3 个主要对象: 1) 表单类 2)关系类(由Form拥有) 3) RestartChecker 类(归 Relation 所有)
当 RestartChecker 获得重新启动的信息时,将此信息槽事件发送到 Relation。关系使用它自己的事件将其发送到 UI 上的表单和表单更改图标。
这是我来自 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;
异步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】:我在这里没有看到死锁,但我看到你用同步调用阻塞了异步方法
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
【讨论】:
感谢您的回复。不,我没有在任何地方使用 .Wait() ,所以 GetLastBootUpTime() 必须以某种方式阻塞线程是真的。但即使我使用您建议的代码 - await Task.Run(() => ... 问题仍然存在 @Blaato 然后我假设你没有提供的另一个代码中的问题...... @Blaato 你能提供额外的代码和\或代码中对Task checker
的所有引用吗?
是的,当然,我编辑了问题,现在我相信有所有重要的代码部分。
@Blaato 你仍然有阻塞同步 GetLastBootUpTime
在异步方法中调用:lastKnownBootUpTime = (isOnline) ? (GetLastBootUpTime(target, useWMI) ?? lastKnownBootUpTime) : lastKnownBootUpTime;
也尝试通过 await Task.Run(()... 运行它。在我看来你没有有一个死锁,但你的异步方法中有同步阻塞调用..以上是关于如何修复异步 WMI 选择/PerformanceCounter 上的 UI 死锁以获取远程计算机 LastBootUpTime的主要内容,如果未能解决你的问题,请参考以下文章