什么是上下文:通过netsh
Processes设置DNS IPv6地址
我做了什么:
public static bool StartProcessAsync(
string filename,
string arguments,
string verb = null,
bool useShell = false,
bool noWindow = false,
bool redirectOut = true,
bool redirectErr = true,
string workingDir = null,
int? timeoutInMs = null,
EventHandler startHandler = null,
EventHandler exitHandler = null
)
{
if (string.IsNullOrWhiteSpace(filename))
return false;
Process newProcess = CreateProcess(filename, arguments, verb, useShell, noWindow, redirectOut, redirectErr, workingDir);
if (newProcess == null)
return false;
newProcess.EnableRaisingEvents = true;
newProcess.Exited += new EventHandler((s, e) =>
{
Debug.WriteLine(filename + " Exited with " + newProcess.ExitCode.ToString() + " exit code.");
string errors = newProcess.StandardError.ReadToEnd();
string output = newProcess.StandardOutput.ReadToEnd();
if (newProcess.ExitCode != 0)
Debug.WriteLine(filename + " exit code: " + newProcess.ExitCode.ToString() + " " + (!string.IsNullOrEmpty(errors) ? " " + errors : "") + " " + (!string.IsNullOrEmpty(output) ? " " + output : ""));
}
);
if (exitHandler != null)
newProcess.Exited += exitHandler;
if (startHandler != null)
startHandler.Invoke(null, null);
return newProcess.Start();
}
我怎么称呼它:
//set new IPv6 DNS addresses
if (ipv6 != null && ipv6.Length > 0)
{
//primary
address = ipv6[0];
try
{
bool res = NetshDNSManaging(ni.Name, true, true, address, startHandler, exitHandler);
}
catch (Exception e)
{ Debug.WriteLine(e.Message); }
if (ipv6.Length > 1)
{
//secondary
address = ipv6[1];
try
{
bool res = NetshDNSManaging(ni.Name, true, false, address, startHandler, exitHandler);
}
catch (Exception e)
{ Debug.WriteLine(e.Message); }
}
}
与那些中间方便的方法:
private static bool NetshDNSManaging(
string interfaceName,
bool isIPv6,
bool isPrimary = true,
string address = null,
EventHandler startHandler = null,
EventHandler exittHandler =null
)
{
if (string.IsNullOrWhiteSpace(interfaceName))
return false;
bool noAddress = (string.IsNullOrWhiteSpace(address));
string args = string.Format("interface {0} {1} dnsservers "{2}"{3} {4} {5}",
isIPv6 ? "ipv6" : "ipv4",//{0}
noAddress?"delete":(isPrimary ? "set" : "add"),//{1}
interfaceName,//{2}
(isPrimary && !noAddress) ? " static" : "",//{3}
noAddress?"all":address,//{4}
noAddress?"":(isPrimary ? "primary" : "index=2")//{5}
);
return StartProcessAsAdminAsync("netsh", args, startHandler, exittHandler);
}
:和
private static bool StartProcessAsAdminAsync(
string filename,
string arguments,
EventHandler startHandler = null,
EventHandler exitHandler = null,
string verb = "runas",
bool useShell = false,
bool noWindow = true,
bool redirectOut = true,
bool redirectErr = true,
string workingDir = null,
int? timeoutInMs = null
)
{
return ProcessUtils.StartProcessAsync(filename, arguments, verb, useShell, noWindow, redirectOut, redirectErr, workingDir, timeoutInMs, startHandler, exitHandler);
}
What is my issue:
有时由于进程代码执行非顺序,第二次调用NetshDNSManaging(ni.Name, true, false, address, startHandler, exitHandler);
(例如:netsh interface ipv6 add dnsservers "Ethernet" 2001:4860:4860::8844 index=2
)在第一次调用NetshDNSManaging(ni.Name, true, true, address, startHandler, exitHandler);
(例如:netsh interface ipv6 set dnsservers "Ethernet" static 2001:4860:4860::8888 primary
)之前完成。
这是只有主DNS设置的结果,因为二级DNS被覆盖(同时被转换为主DNS,我猜是netsh
,因为它是第一个完成的)
结果:
EventHandler startHandler = new EventHandler(ProcessStarted);
EventHandler exitHandler = new EventHandler(ProcessExited);
void ProcessExited(object? sender, EventArgs e)
{
_procNumber = Math.Max(_procNumber - 1, 0);
if (_procNumber == 0)
{
IsWaiting = false;
}
}
void ProcessStarted(object? sender, EventArgs e)
{
while (_procNumber > 0) ; //Kinda ugly synchronization !!!
_procNumber++;
IsWaiting = true;
}
和IsWaiting
属性通知UI是否显示等待轮动画。
所以这是有效的,但我不满意,我认为这不是一个好的做法。
我想要的:只在第一个调用完成时才开始第二个调用(因此顺序调用2创建的进程),但不阻塞主WPF线程(让我的UI显示等待动画轮)。
所以类似:主UI线程有一个List<Process>
(我的例子只有2个进程,但可以推广到更多的几个),当需要时,它应该在列表中一个接一个地启动进程,按顺序,第二个等待第一个开始前完成,等等,直到列表结束。但是不阻塞主WPF UI线程!
并行是我的一个宿敌,我为此而挣扎。
感谢@TamBui,我找到了他建议的await/async解决方案。
首先,我想分享YouTube教程,我发现它非常具有教育意义:https://www.youtube.com/watch?v=2moh18sh5p4
下面是我修改的内容:
-
我方便的方法:
private static Process NetshDNSManaging( string interfaceName, bool isIPv6, bool isPrimary = true, string address = null, EventHandler startHandler = null, EventHandler exittHandler =null ) { if (string.IsNullOrWhiteSpace(interfaceName)) return null; bool noAddress = (string.IsNullOrWhiteSpace(address)); string args = string.Format("interface {0} {1} dnsservers "{2}"{3} {4} {5}", isIPv6 ? "ipv6" : "ipv4",//{0} noAddress?"delete":(isPrimary ? "set" : "add"),//{1} interfaceName,//{2} (isPrimary && !noAddress) ? " static" : "",//{3} noAddress?"all":address,//{4} noAddress?"":(isPrimary ? "primary" : "index=2")//{5} ); return GetProcessAsAdmin("netsh", args, startHandler, exittHandler); } private static Process GetProcessAsAdmin( string filename, string arguments, EventHandler startHandler = null, EventHandler exitHandler = null, string verb = "runas", bool useShell = false, bool noWindow = true, bool redirectOut = true, bool redirectErr = true, string workingDir = null, int? timeoutInMs = null ) { return ProcessUtils.CreateProcess(filename, arguments, startHandler, exitHandler, verb, useShell, noWindow, redirectOut, redirectErr, workingDir); }
-
进程管理的实用程序:
public static class ProcessUtils { public static async Task<bool> StartChainedProcessAsync(List<Process> processes) { if (processes == null || !processes.Any()) return false; bool cumulativeExitStatus = true; foreach (var p in processes) { cumulativeExitStatus &= await StartProcessAsync(p); } return cumulativeExitStatus; } public static async Task<bool> StartProcessAsync(Process p) { if (p == null) return false; p.Start(); await p.WaitForExitAsync(); return p.ExitCode == 0; } public static Process CreateProcess( string filename, string arguments, EventHandler startHandler = null, EventHandler exitHandler = null, string verb = null, bool useShell = false, bool noWindow = false, bool redirectOut = true, bool redirectErr = true, string workingDir = null //int? timeoutInMs = null, ) { /// set <see cref="ProcessStartInfo"/> of the <see cref="Process"/> to return; ProcessStartInfo psi = new ProcessStartInfo() { FileName = filename, Arguments = arguments, UseShellExecute = useShell, CreateNoWindow = noWindow, RedirectStandardError = redirectErr, RedirectStandardOutput = redirectOut }; if (verb != null) psi.Verb = verb; if (workingDir != null) psi.WorkingDirectory = workingDir; Process p = new Process(); p.StartInfo = psi; p.EnableRaisingEvents = true; p.Exited += new EventHandler((s, e) => { Debug.WriteLine(filename + " Exited with " + p.ExitCode.ToString() + " exit code."); string errors = p.StandardError.ReadToEnd(); string output = p.StandardOutput.ReadToEnd(); if (p.ExitCode != 0) Debug.WriteLine("Errors : " + (!string.IsNullOrEmpty(errors) ? " " + errors : "") + "nOutputs : " + (!string.IsNullOrEmpty(output) ? " " + output : "")); } ); if (exitHandler != null) p.Exited += exitHandler; if (startHandler != null) startHandler.Invoke(null, null); return p; } }
-
呼叫他们:
public static async Task SetDNS(this NetworkInterface ni, EventHandler startHandler, EventHandler exitHandler, string[] ipv4, string[] ipv6) { if (!(ipv4.Any(add => IsIP(add, RGXV4)) || ipv6.Any(add => IsIP(add, RGXV6)))) { Debug.WriteLine("None of the suplied addresses are IPv4/6.nCan not update DNS IP addresses."); return; } List<Process> chainedProcess = new List<Process>(); // delete current IPv4 DNS chainedProcess.Add(NetshDNSManaging(ni.Name, false)); // delete current IPv6 DNS chainedProcess.Add(NetshDNSManaging(ni.Name, true)); string address; //set new IPv6 DNS addresses if (ipv6 != null && ipv6.Length > 0) { //primary address = ipv6[0]; try { chainedProcess.Add(NetshDNSManaging(ni.Name, true, true, address/*, startHandler, exitHandler*/)); } catch (Exception e) { Debug.WriteLine(e.Message); } if (ipv6.Length > 1) { //secondary address = ipv6[1]; try { chainedProcess.Add(NetshDNSManaging(ni.Name, true, false, address/*, startHandler, exitHandler*/)); } catch (Exception e) { Debug.WriteLine(e.Message); } } } startHandler.Invoke(null, null); await ProcessUtils.StartChainedProcessAsync(chainedProcess); exitHandler.Invoke(null, null); //set new IPv4 DNS addresses if (ipv4 != null && ipv4.Length > 0) { //Use legacy (WMI win32) version for better performance on some ipv4 DNS addresses (observed especially on FDN ipv4 addresses) SetLegacyDNS(ni, ipv4); } }
-
最后从我的主ViewModel开始调用:
EventHandler startHandler = new EventHandler(ProcessStarted); EventHandler exitHandler = new EventHandler(ProcessExited); SelectedNI.Ni.SetDNS(startHandler, exitHandler, ipv4, ipv6); void ProcessExited(object? sender, EventArgs e) { IsWaiting = false; } void ProcessStarted(object? sender, EventArgs e) { IsWaiting = true; }
也许我应该摆脱这最后2回调事件处理程序现在?