C# 任务返回c中的StreamReader#

C# 任务返回c中的StreamReader#,c#,async-await,task,C#,Async Await,Task,我在C#中有一个任务,它应该返回DISM的标准输出,因此我可以在需要的地方使用它: public async Task<StreamReader> DISM(string Args) { StreamReader DISMstdout = null; await Task.Run(() => { Process DISMcmd = new Process(); if (Environment.Is64BitOperat

我在C#中有一个任务,它应该返回DISM的标准输出,因此我可以在需要的地方使用它:

public async Task<StreamReader> DISM(string Args)
{

   StreamReader DISMstdout = null;

    await Task.Run(() =>
    {
        Process DISMcmd = new Process();

        if (Environment.Is64BitOperatingSystem)
        {
            DISMcmd.StartInfo.FileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "SysWOW64", "dism.exe");
        }
        else
        {
            DISMcmd.StartInfo.FileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "System32", "dism.exe");
        }

        DISMcmd.StartInfo.Verb = "runas";

        DISMcmd.StartInfo.Arguments = DISMArguments;

        DISMcmd.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
        DISMcmd.StartInfo.CreateNoWindow = true;
        DISMcmd.StartInfo.UseShellExecute = false;
        DISMcmd.StartInfo.RedirectStandardOutput = true;
        DISMcmd.EnableRaisingEvents = true;
        DISMcmd.Start();

        DISMstdout = DISMcmd.StandardOutput;

        DISMcmd.WaitForExit();
    });
    return DISMstdout;
}

这段代码有什么问题

与传统的异步方法不同,我编写方法来利用
async..wait
的方式如下:

public async Task<TResult> WithDism<TResult>(string args, Func<StreamReader, Task<TResult>> func)
{
    return await Task.Run(async () =>
    {
        var proc = new Process();

        var windowsDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
        var systemDir = Environment.Is64BitOperatingSystem ? "SysWOW64" : "System32";
        proc.StartInfo.FileName = Path.Combine(windowsDir, systemDir, "dism.exe");

        proc.StartInfo.Verb = "runas";

        proc.StartInfo.Arguments = args;

        proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
        proc.StartInfo.CreateNoWindow = true;
        proc.StartInfo.UseShellExecute = false;
        proc.StartInfo.RedirectStandardOutput = true;
        proc.Start();

        Console.Error.WriteLine("dism started");

        var result = await func(proc.StandardOutput);

        Console.Error.WriteLine("func finished");
        // discard rest of stdout
        await proc.StandardOutput.ReadToEndAsync();
        proc.WaitForExit();

        return result;
    });
}
var task = WithDism("/?", async sr => await sr.ReadToEndAsync()); // or process line-by-line
Console.WriteLine("dism task running");
Console.WriteLine(await task);
它产生以下输出

dism任务正在运行
dism已启动
func已完成

错误:740

运行DISM需要提升权限。
使用提升的命令提示符完成这些任务


请注意,在使用子流程时,您的工作是确保它们正确退出或关闭,以避免留下僵尸流程。这就是为什么我添加了可能冗余的
ReadToEndAsync()
——如果
func
仍然保留一些未使用的输出,这将允许进程达到其自然结束

但是,这意味着调用函数只会在发生这种情况时继续。如果您留下了很多您不感兴趣的未使用的输出,这将导致不必要的延迟。您可以通过将此清理衍生到不同的后台任务并立即使用以下方法返回结果来解决此问题:

Task.Run(() => {
    // discard rest of stdout and clean up process:
    await proc.StandardOutput.ReadToEndAsync();
    proc.WaitForExit();
});
但我承认我在这方面有点冒险,我不完全确定让一项任务像那样“疯狂运行”的稳健性。当然,清理流程的适当方式将取决于在您从
func
获得想要返回的输出后,流程实际在做什么



我使用同步调用来控制台,因为它们只用于说明事件的时间,我想知道当执行达到那个点时。通常,您会以一种“病毒式”的方式使用异步,以确保控制尽快传回顶层。

在使用Benchmark.NET对其进行处理之后,似乎启动一个过程(我尝试过DISM和Atom以获得一些重要功能)-从安装到
启动()
,需要大约50毫秒。这对我来说似乎微不足道。毕竟,50毫秒的延迟对于玩传奇联盟来说已经足够好了,而且你不会在一个很紧的循环中开始这些

我想提供另一个答案“不要为Task.Run()费心,直接使用异步I/O”除非您绝对需要消除这种延迟,并且相信从后台线程派生会有所帮助:

static string GetDismPath()
{
    var windowsDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
    var systemDir = Environment.Is64BitOperatingSystem ? "SysWOW64" : "System32";
    var dismExePath = Path.Combine(windowsDir, systemDir, "dism.exe");

    return dismExePath;
}

static Process StartDism(string args)
{
    var proc = new Process
    {
        StartInfo =
        {
            FileName = GetDismPath(),
            Verb = "runas",
            Arguments = args,
            WindowStyle = ProcessWindowStyle.Hidden,
            CreateNoWindow = true,
            UseShellExecute = false,
            RedirectStandardOutput = true
        }
    };

    proc.Start();

    return proc;
}
static void Cleanup(Process proc)
{
    Task.Run(async () =>
    {
        proc.StandardInput.Close();
        var buf = new char[0x1000];
        while (await proc.StandardOutput.ReadBlockAsync(buf, 0, buf.Length).ConfigureAwait(false) != 0) { }
        while (await proc.StandardError.ReadBlockAsync(buf, 0, buf.Length).ConfigureAwait(false) != 0) { }

        if (!proc.WaitForExit(5000))
        {
            proc.Kill();
        }
        proc.Dispose();
    });
}
static async Task Main(string[] args)
{
    var dismProc = StartDism("/?");

    // do what you want with the output
    var dismOutput = await dismProc.StandardOutput.ReadToEndAsync().ConfigureAwait(false);

    await Console.Out.WriteAsync(dismOutput).ConfigureAwait(false);
    Cleanup(dismProc);
}


我只使用
Task.Run()
将清理工作从主线程中移除,以防在DISM继续生成您不感兴趣的、不希望立即终止的输出时需要执行其他操作。

在进程退出后返回进程stdout的流,在这一点上,进程不再存在,我猜它的标准IO流也不存在-.NET将不得不保留它们,直到它们为您读取。在调用
WaitForExit()
之前,尝试从流中读取,我打赌您的数据将在那里。我会做一些类似于将回调传递给从流中读取的
DISM()
,并将回调的结果作为方法的返回值返回。然后,当您通过
wait DISM(…)
最后一个但并非最不重要的是,我不希望
ReadLine()
在调用
ReadToEnd()后返回任何内容时,整个shebang就会执行
ReadToEnd()
并不意味着“读取当前可用的所有数据”,它意味着“在流关闭之前阻止,并且不再有可用的数据”。在读取程序的所有输出之前,程序无法退出。所以WaitForExit()很可能会死锁。目前还不清楚你为什么需要它,但是如果你这样做的话,考虑退出的事件。改为使用StringReader。尝试将调用方法更改为:
StreamReader DISMoutput=wait this.DISM(“…”)。(否
new
)完全消除任务。Run()lambda。在另一种方法中,消除
DISMcmd.EnableRaisingEvents=true。应该是异步的。并检查你传递的论点。@Jimi-最后一部分是“视情况而定”之类的东西。如果您可以依赖传递到
func
和DISM
消耗整个流,那么它几乎没有什么区别。但是,如果说,你只在输出中间寻找一条特定的线,那么在返回结果之前等待所有的后面的行是没有意义的。事实上,甚至可能没有理由让它完成运行,因此您可能希望中断它,而不是读取可能有许多不必要的输出。但由于是.NET/Windows,您不能只向进程发送SIGINT,因此,您可以自行决定如何干净地或不干净地关闭生成的流程。这是因为流程通常使用自己的事件进行处理(它构建为事件驱动,不同于异步模式,但仍然有效),因此您可以订阅其退出的
事件(使用
.EnableRaisingEvents=true;
)并最终使用超时/结束逻辑中断进程(有点苛刻,但
Proces.Kill
引发
退出的
事件)=>关于(不同的语言,我同意:)@Jimi所以从技术上讲,在这里使用async/Wait你甚至不需要事件,你只需要阅读你需要的,设置一个计时器,在后台任务中终止进程,如果它仍然运行?@Jimi出于好奇,我发布了这个答案的一个稍加修改的版本给代码审查,你可能想在那里回答你的评论,至少进行一次UPF投票只读存储器:
static string GetDismPath()
{
    var windowsDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
    var systemDir = Environment.Is64BitOperatingSystem ? "SysWOW64" : "System32";
    var dismExePath = Path.Combine(windowsDir, systemDir, "dism.exe");

    return dismExePath;
}

static Process StartDism(string args)
{
    var proc = new Process
    {
        StartInfo =
        {
            FileName = GetDismPath(),
            Verb = "runas",
            Arguments = args,
            WindowStyle = ProcessWindowStyle.Hidden,
            CreateNoWindow = true,
            UseShellExecute = false,
            RedirectStandardOutput = true
        }
    };

    proc.Start();

    return proc;
}
static void Cleanup(Process proc)
{
    Task.Run(async () =>
    {
        proc.StandardInput.Close();
        var buf = new char[0x1000];
        while (await proc.StandardOutput.ReadBlockAsync(buf, 0, buf.Length).ConfigureAwait(false) != 0) { }
        while (await proc.StandardError.ReadBlockAsync(buf, 0, buf.Length).ConfigureAwait(false) != 0) { }

        if (!proc.WaitForExit(5000))
        {
            proc.Kill();
        }
        proc.Dispose();
    });
}
static async Task Main(string[] args)
{
    var dismProc = StartDism("/?");

    // do what you want with the output
    var dismOutput = await dismProc.StandardOutput.ReadToEndAsync().ConfigureAwait(false);

    await Console.Out.WriteAsync(dismOutput).ConfigureAwait(false);
    Cleanup(dismProc);
}