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. 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. When I run this check asynchroniously, ManagementObjectSearcher trigger deadlock, when using it's .Get() method. The same problem I have when I use PerformanceCounter instead.
There is 3 main objects for this: 1) Form class 2) Relation class (owned by Form) 3) RestartChecker class (owned by Relation)
When RestartChecker get info that restart was made, sends this info trough event to Relation. Relation use it's own event to sends it to Form and Form change icon on UI.
Here is my code (important parts) from RestartChecker:
This method is in Relation class and it's starts the RestartChecker. This Relation method is called from Form class.
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
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
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:
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:
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
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
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)
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