最好的方式有多个顺序进程运行而不阻塞主WPF UI线程?



什么是上下文:通过netshProcesses设置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

下面是我修改的内容:

  1. 我方便的方法:

    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);
    }
    
  2. 进程管理的实用程序:

    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;
    }
    }
    
  3. 呼叫他们:

    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);
    }
    }
    
  4. 最后从我的主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回调事件处理程序现在?

最新更新