You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF不阻塞主线程顺序执行多个Process的最佳实现方案?

方案核心

将原有的基于事件的异步进程调用(EAP)转换为.NET标准的基于任务的异步模式(TAP),通过await关键字实现无阻塞的顺序执行,全程不会占用WPF主线程,UI动画可正常渲染。


步骤1:改造通用进程启动方法,支持await

把原来返回bool的StartProcessAsync改造成返回Task<int>(Task的结果为进程退出码),使用TaskCompletionSource封装进程退出事件:

public static Task<int> 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,
    CancellationToken cancellationToken = default)
{
    if (string.IsNullOrWhiteSpace(filename))
        return Task.FromResult(-1);
    
    Process newProcess = CreateProcess(filename, arguments, verb, useShell, noWindow, redirectOut, redirectErr, workingDir);
    if (newProcess == null)
        return Task.FromResult(-1);

    var tcs = new TaskCompletionSource<int>();

    newProcess.EnableRaisingEvents = true;
    newProcess.Exited += (s, e) => 
    {
        try
        {
            Debug.WriteLine($"{filename} Exited with {newProcess.ExitCode} exit code.");
            string errors = newProcess.StandardError.ReadToEnd();
            string output = newProcess.StandardOutput.ReadToEnd();
            if (newProcess.ExitCode != 0)
                Debug.WriteLine($"{filename} exit code: {newProcess.ExitCode} {errors} {output}");

            exitHandler?.Invoke(s, e);
            tcs.TrySetResult(newProcess.ExitCode);
        }
        catch (Exception ex)
        {
            tcs.TrySetException(ex);
        }
        finally
        {
            newProcess.Dispose();
        }
    };

    // 可选处理取消/超时逻辑
    if (timeoutInMs.HasValue || cancellationToken.CanBeCanceled)
    {
        cancellationToken.Register(() => 
        {
            try { newProcess.Kill(); } catch { }
            tcs.TrySetCanceled();
        });

        if (timeoutInMs.HasValue)
        {
            var cts = new CancellationTokenSource(timeoutInMs.Value);
            cts.Token.Register(() => 
            {
                try { newProcess.Kill(); } catch { }
                tcs.TrySetException(new TimeoutException($"Process {filename} timed out"));
            });
        }
    }

    try
    {
        startHandler?.Invoke(null, null);
        if (!newProcess.Start())
        {
            tcs.TrySetResult(-1);
        }
    }
    catch (Exception ex)
    {
        tcs.TrySetException(ex);
    }

    return tcs.Task;
}

步骤2:改造上层封装方法,同步适配异步模式

private static Task<int> 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,
    CancellationToken cancellationToken = default)
{
    return ProcessUtils.StartProcessAsync(filename, arguments, verb, useShell, noWindow, redirectOut, redirectErr, workingDir, timeoutInMs, startHandler, exitHandler, cancellationToken);
}
private static Task<int> NetshDNSManaging(
    string interfaceName,
    bool isIPv6,
    bool isPrimary = true,
    string address = null,
    EventHandler startHandler = null,
    EventHandler exitHandler = null,
    CancellationToken cancellationToken = default)
{
    if (string.IsNullOrWhiteSpace(interfaceName))
        return Task.FromResult(-1);
    bool noAddress = string.IsNullOrWhiteSpace(address);
    string args = string.Format("interface {0} {1} dnsservers \"{2}\"{3} {4} {5}",
                               isIPv6 ? "ipv6" : "ipv4",
                               noAddress?"delete":(isPrimary ? "set" : "add"),
                               interfaceName,
                               (isPrimary && !noAddress) ? " static" : "",
                               noAddress?"all":address,
                               noAddress?"":(isPrimary ? "primary" : "index=2")
                               );
    return StartProcessAsAdminAsync("netsh", args, startHandler, exitHandler, cancellationToken: cancellationToken);
}

步骤3:顺序执行多个进程的调用逻辑

WPF的事件处理/命令方法可直接加async修饰,await会自动捕获UI同步上下文,不会阻塞主线程:

// 示例为WPF按钮点击事件,可替换为你自己的业务入口方法
private async void ApplyDnsSettings_Click(object sender, RoutedEventArgs e)
{
    if (ipv6 == null || ipv6.Length == 0)
        return;

    try
    {
        // 启动等待动画,直接操作UI属性无跨线程问题
        IsWaiting = true;

        // 执行主DNS设置,等待进程完全退出后才会执行后续代码
        var primaryResult = await NetshDNSManaging(ni.Name, true, true, ipv6[0]);
        if (primaryResult != 0)
        {
            Debug.WriteLine("主DNS设置失败");
            return;
        }

        if (ipv6.Length > 1)
        {
            // 执行次DNS设置,严格在主DNS设置完成后启动
            var secondaryResult = await NetshDNSManaging(ni.Name, true, false, ipv6[1]);
            if (secondaryResult != 0)
            {
                Debug.WriteLine("次DNS设置失败");
                return;
            }
        }

        // 扩展到任意数量进程只需改为循环:
        // for (int i = 0; i < dnsList.Count; i++)
        // {
        //     await NetshDNSManaging(ni.Name, true, i == 0, dnsList[i]);
        // }

        Debug.WriteLine("所有DNS设置完成");
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"DNS设置出错:{ex.Message}");
    }
    finally
    {
        // 关闭等待动画
        IsWaiting = false;
    }
}

方案优势

  • 无自旋等待,不会浪费CPU资源,await期间WPF主线程可正常响应UI操作,等待动画不会卡顿
  • 执行顺序严格可控,前一个进程完全退出后才会启动后一个进程,彻底解决配置覆盖问题
  • 支持任意数量的进程顺序执行,仅需扩展循环逻辑即可
  • 可选支持超时和取消逻辑,可根据需求传入CancellationToken实现中途取消操作

内容的提问来源于stack exchange,提问作者TRex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 03:24:03