簡體   English   中英

任務返回c#中的StreamReader

[英]Task return a StreamReader in c#

我在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;
}

但它並沒有真正起作用。 如果我想從另一個任務中讀取標准輸出我不能(因為它是空的)所以我的任務一定有問題嗎?

public async Task Test()
{
    await Task.Run(() =>
    {

    StreamReader DISM = await new DISM("/Get-ImageInfo /ImageFile:" + ImagePath + @" /Index:1");

    string data = string.Empty;
     MessageBox.Show(DISM.ReadToEnd()); // this should display a msgbox with the standardoutput of dism

     while ((data = DISM.ReadLine()) != null)
     {
         if (data.Contains("Version : "))
         {
               // do something
         }
     }
   }); 
}

這段代碼有什么問題?

我編寫你的方法來利用async..await而不是遺留異步方法的方式是這樣的:

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開始了
功能完成了

錯誤:740

運行DISM需要提升權限。
使用提升的命令提示符完成這些任務。


請注意,在使用子進程時,確保它們正確退出或關閉以避免離開僵屍進程是你的工作。 這就是為什么我添加了可能冗余的ReadToEndAsync() - 如果func仍然留下一些未消耗的輸出,這應該允許進程達到其自然結束。

但是,這意味着調用函數只有在發生這種情況時才會繼續。 如果你留下了許多你不感興趣的未消耗的輸出,這將導致不必要的延遲。 您可以通過將此清理產生到不同的后台任務並使用以下內容立即返回結果來解決此問題:

Task.Run(() => {
    // discard rest of stdout and clean up process:
    await proc.StandardOutput.ReadToEndAsync();
    proc.WaitForExit();
});

但是我承認我在那里走得很遠,我不能完全確定只是讓任務“瘋狂”的強大性。 當然,清理進程的適當方法取決於在獲得要從func返回的輸出后它實際執行的操作。


我在那里使用同步調用Console,因為它們只用於說明事件的時間,我想知道執行到達那一點。 通常,您將以“病毒式”方式使用異步,以確保控制盡快返回頂級。

在使用Benchmark.NET玩這個之后,似乎啟動一個進程(我嘗試使用DISM和Atom來獲得大量的東西) - 從setup到Start() - 大約需要50毫秒。 對於這種用途,這似乎是微不足道的。 畢竟,50ms是足夠好的延遲,比如說英雄聯盟,並且你不會在緊張的循環中開始這些。

我想提供另一個答案“不要煩擾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一直產生你不感興趣的輸出你不想直接殺死。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM