如何在 cmd.exe window 作为后台工作程序 C# 中的进程时关闭它?
How to close cmd.exe window while its being ran as a process in a background worker C#?
我是 C# 的新手,正在尝试了解后台工作人员。
目前,当我 运行 这段代码时,它会在单击 StopButton 并留下 "cancelled" 消息后停止重定向并从命令提示符读取输出,但之后什么都不做。我目前可能正在实施这一切错误,但我通过单击调用 CancelAsync() 的停止按钮设置了 e.Cancel,这将变为 CancellationPending = true。有人知道我应该怎么做吗?
非常感谢!!
感谢您的帮助!
public Form2()
{
InitializeComponent();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
private void StopButton_Click(object sender, EventArgs e)
{
if (bw.WorkerSupportsCancellation == true)
{
bw.CancelAsync();
bw.Dispose();
Invoke(new ToDoDelegate(() => this.textBox2.Text += Environment.NewLine + "Cancelled " + Environment.NewLine));
}
}
private void Submit_Click(object sender, EventArgs e)
{
if(bw.IsBusy != true)
{
bw.RunWorkerAsync();
Invoke(new ToDoDelegate(() => this.textBox2.Text += "Starting Download " + Environment.NewLine));
}
}
public bool DoSVNCheckout(String KeyUrl, DoWorkEventArgs e)
{
SVNProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/C plink download files using svn"
Verb = "runas",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = false,
}
};
SVNProcess.Start();
while(!SVNProcess.StandardOutput.EndOfStream & bw.CancellationPending == false)
{
string output = SVNProcess.StandardOutput.ReadLine();
Invoke(new ToDoDelegate(() => this.textBox2.Text += output));
}
while (!SVNProcess.StandardError.EndOfStream & bw.CancellationPending == false)
{
string Erroutput = SVNProcess.StandardError.ReadLine();
Invoke(new ToDoDelegate(() => this.textBox2.Text += Erroutput));
}
if(SVNProcess.StandardError.EndOfStream & bw.CancellationPending == false)
{
string Erroutput = SVNProcess.StandardError.ReadLine();
Invoke(new ToDoDelegate(() => this.textBox2.Text += Erroutput));
}
//if I manually close the cmd.exe window by clicking X
//in the top right corner the program runs correctly
//at these lines of code right here
if(bw.CancellationPending == true)
{
e.Cancel = true;
return true;
}
return false;
}
private delegate void ToDoDelegate();
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
if(bw.CancellationPending == true)
{
e.Cancel = true;
return;
}
e.Cancel = DoSVNCheckout(URL, e);
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
this.textBox2.Text = "Canceled!";
}
else{
this.textBox2.Text = "Done!";
}
}
您编写的代码存在许多问题。在我看来,两个主要问题是:
- 首先,您似乎将
BackgroundWorker
操作与您已经启动的单独 运行 过程混淆了。两者绝不相同,甚至互不相关。取消 BackgroundWorker
不会对您启动的进程产生任何直接影响。您的问题不清楚这里实际需要的行为是什么,但是您没有做任何事情来实际终止外部进程。充其量,如果您不阻止等待进程产生输出的 DoWork
方法,您将放弃该进程。实际上,如果不终止进程,您的 DoWork
永远不会注意到您已尝试取消它,因为它卡在 ReadLine()
调用上。
- 您正在以串行方式使用
StandardOutput
和 StandardError
流,即一个接一个地使用。文档明确警告不要这样做,因为这是使代码死锁的一种非常可靠的方法。每个流的缓冲区相对较小,如果在缓冲区已满时尝试写入这些流之一,整个外部进程将挂起。这将反过来导致不再将输出写入任何流。在您的代码示例中,如果 StandardError
流缓冲区在您能够完全读取 StandardOutput
流之前变满,则外部进程将挂起,您自己的进程也会挂起。
另一个小问题是您没有利用 BackgroundWorker.ProgressChanged
事件,您可以使用该事件将从输出和错误字符串中读取的文本传递回 UI 线程,您可以在其中将该文本添加到文本框。这里使用 Control.Invoke()
并不是绝对必要的,并且不能充分利用 BackgroundWorker
.
中的功能
有多种方法可以修改您编写的代码,这样您仍然可以使用 BackgroundWorker
并实现您的目标。您可以做出的一项明显改进是将 Process
对象引用存储在实例字段中,以便 StopButton_Click()
方法可以访问它。在该方法中,您可以调用 Process.Kill()
方法来实际终止进程。
但即便如此,您仍需要修复现有的容易发生死锁的实现。这可以通过多种方式完成:使用 Process.OutputDataReceived
和 Process.ErrorDataReceived
事件;创建第二个 BackgroundWorker
任务来处理其中一个流;使用基于 Task
的习语来阅读流。
最后一个选项是我的偏好。第二个选项不必要地创建 long-运行 线程,而基于事件的模式(第一个选项)使用起来很尴尬(并且是基于行的,因此在处理期间写入部分行的进程时价值有限他们的操作过程)。但是如果你打算使用基于 Task
的习语来读取流,在我看来你应该升级整个实现来这样做。
如果有人愿意,BackgroundWorker
仍然是可行的 class,但是新的 Task
功能与 async
/await
相结合关键字为恕我直言提供了一种更简单、更清晰的处理异步操作的方法。最大的优点之一是它不依赖于显式使用的线程(例如 运行 线程池线程中的 DoWork
事件处理程序)。异步 I/O 操作,例如构成整个场景的内容,通过 API 隐式处理,允许您编写的所有代码在您所在的 UI 线程中执行想要。
这是您的示例的一个版本:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private TaskCompletionSource<bool> _cancelTask;
private async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
button2.Enabled = true;
_cancelTask = new TaskCompletionSource<bool>();
try
{
await RunProcess();
}
catch (TaskCanceledException)
{
MessageBox.Show("The operation was cancelled");
}
finally
{
_cancelTask = null;
button1.Enabled = true;
button2.Enabled = false;
}
}
private void button2_Click(object sender, EventArgs e)
{
_cancelTask.SetCanceled();
}
private async Task RunProcess()
{
Process process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/C pause",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = false,
}
};
process.Start();
Task readerTasks = Task.WhenAll(
ConsumeReader(process.StandardError),
ConsumeReader(process.StandardOutput));
Task completedTask = await Task.WhenAny(readerTasks, _cancelTask.Task);
if (completedTask == _cancelTask.Task)
{
process.Kill();
await readerTasks;
throw new TaskCanceledException(_cancelTask.Task);
}
}
private async Task ConsumeReader(TextReader reader)
{
char[] text = new char[512];
int cch;
while ((cch = await reader.ReadAsync(text, 0, text.Length)) > 0)
{
textBox1.AppendText(new string(text, 0, cch));
}
}
}
备注:
- 首先,如您所见,根本不再需要
BackgroundWorker
。 async
/await
模式隐含地完成了 BackgroundWorker
为您完成的所有相同工作,但没有设置和管理它所需的所有额外样板代码。
- 有一个新的实例字段,
_cancelTask
,代表一个可以完成的简单Task
对象。它只能通过被取消来完成,在这种情况下,但这并不是严格要求的……您会注意到监视任务完成的 await
语句实际上并不关心任务是如何结束的。就这样。在更复杂的场景中,对于这样一个 Task
对象,可能实际上想要使用 Result
,调用 SetResult()
来完成任务,并使用 SetCanceled()
实际上取消所代表的操作。这一切都取决于具体的上下文。
button1_Click()
方法(等同于你的Submit_Click()
方法)写得好像一切都是同步发生的。通过await
语句的"magic",该方法实际上分两部分执行。单击该按钮时,将执行 await
语句之前的所有内容;在 await
,一旦 RunProcess()
方法 returned,button1_Click()
方法 returns。它将在稍后恢复执行,当 RunProcess()
编辑的 Task
对象 return 完成时,这反过来会在该方法结束时发生(即 not 第一次 returns).
- 在
button1_Click()
方法中,更新 UI 以反映当前的操作状态:开始按钮已禁用,取消按钮已启用。在 returning 之前,按钮会 returned 到它们的原始状态。
button1_Click()
方法也是创建并随后丢弃 _cancelTask
对象的地方。如果 RunProcess()
抛出,await RunProcess()
语句将看到 TaskCanceledException
;这用于向用户显示 MessageBox
报告操作已被取消。您当然可以按照您认为合适的方式响应此类异常。
- 这样,
button2_Click()
方法(相当于你的StopButton_Click()
方法)只需将_cancelTask
对象设置为完成状态,在本例中通过调用SetCanceled()
.
RunProcess()
方法是进程的主要处理发生的地方。它启动进程,然后等待相关任务完成。表示输出流和错误流的两个任务包装在对 Task.WhenAll()
的调用中。这将创建一个新的 Task
对象,该对象仅在所有包装任务完成时才会完成。然后该方法通过 Task.WhenAny()
等待包装器任务和 _cancelTask
对象。如果或完成,该方法将完成执行。如果完成的任务是 _cancelTask
对象,该方法通过终止启动的进程(在它正在做的事情中中断它),等待进程实际退出(可以通过包装器任务的完成......当输出流和错误流都到达末尾时,这些完成,这又发生在进程退出时),然后抛出 TaskCanceledException
.
ConsumeReader()
方法是一种辅助方法,它简单地从给定的 TextReader
对象中读取文本,并将输出附加到文本框。它使用 TextReader.ReadAsync()
;这种类型的方法也可以使用 TextReader.ReadLineAsync()
来编写,但在那种情况下,您只会在每行的末尾看到输出。使用 ReadAsync()
可确保在输出可用时立即检索输出,而无需等待换行符。
- 请注意,
RunProcess()
和 ConsumeReader()
方法也是 async
,并且其中也有 await
语句。与 button1_Click()
一样,这些方法最初 return 在到达 await
语句时执行,稍后在等待的 Task
完成时恢复执行。在 ConsumeReader()
示例中,您还会注意到 await
解压了 int
值,即 Task<int>
的 Result
属性 值正在等待。 await
语句构成一个表达式,计算结果为等待的 Task
的 Result
值。
- 在每种情况下使用
await
的一个非常重要的特征是框架在 UI 线程 上恢复方法 的执行.这就是为什么button1_Click()
方法在await
之后仍然可以访问UI对象button1
和button2
,以及为什么ConsumeReader()
可以访问textBox1
对象,目的是每次通过 ReadAsync()
方法 return 编辑时附加文本。
我意识到以上内容可能需要消化很多。特别是当其中大部分涉及从使用 BackgroundWorker
到基于 Task
的 API 的完全改变时,而不是解决我一开始提到的主要两个问题。但我希望您能看到这些更改是如何隐含地解决这些问题的,以及如何通过使用现代 async
/[=36 以更简单、更易于阅读的方式满足代码的其他要求=] 模式。
为了完整起见,这里是与上述 Form1
class:
一起出现的 Designer 生成的代码
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(12, 12);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "Start";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Enabled = false;
this.button2.Location = new System.Drawing.Point(93, 12);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 0;
this.button2.Text = "Stop";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// textBox1
//
this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBox1.Location = new System.Drawing.Point(13, 42);
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.ReadOnly = true;
this.textBox1.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.textBox1.Size = new System.Drawing.Size(488, 258);
this.textBox1.TabIndex = 1;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(513, 312);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.TextBox textBox1;
}
我是 C# 的新手,正在尝试了解后台工作人员。
目前,当我 运行 这段代码时,它会在单击 StopButton 并留下 "cancelled" 消息后停止重定向并从命令提示符读取输出,但之后什么都不做。我目前可能正在实施这一切错误,但我通过单击调用 CancelAsync() 的停止按钮设置了 e.Cancel,这将变为 CancellationPending = true。有人知道我应该怎么做吗?
非常感谢!! 感谢您的帮助!
public Form2()
{
InitializeComponent();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
private void StopButton_Click(object sender, EventArgs e)
{
if (bw.WorkerSupportsCancellation == true)
{
bw.CancelAsync();
bw.Dispose();
Invoke(new ToDoDelegate(() => this.textBox2.Text += Environment.NewLine + "Cancelled " + Environment.NewLine));
}
}
private void Submit_Click(object sender, EventArgs e)
{
if(bw.IsBusy != true)
{
bw.RunWorkerAsync();
Invoke(new ToDoDelegate(() => this.textBox2.Text += "Starting Download " + Environment.NewLine));
}
}
public bool DoSVNCheckout(String KeyUrl, DoWorkEventArgs e)
{
SVNProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/C plink download files using svn"
Verb = "runas",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = false,
}
};
SVNProcess.Start();
while(!SVNProcess.StandardOutput.EndOfStream & bw.CancellationPending == false)
{
string output = SVNProcess.StandardOutput.ReadLine();
Invoke(new ToDoDelegate(() => this.textBox2.Text += output));
}
while (!SVNProcess.StandardError.EndOfStream & bw.CancellationPending == false)
{
string Erroutput = SVNProcess.StandardError.ReadLine();
Invoke(new ToDoDelegate(() => this.textBox2.Text += Erroutput));
}
if(SVNProcess.StandardError.EndOfStream & bw.CancellationPending == false)
{
string Erroutput = SVNProcess.StandardError.ReadLine();
Invoke(new ToDoDelegate(() => this.textBox2.Text += Erroutput));
}
//if I manually close the cmd.exe window by clicking X
//in the top right corner the program runs correctly
//at these lines of code right here
if(bw.CancellationPending == true)
{
e.Cancel = true;
return true;
}
return false;
}
private delegate void ToDoDelegate();
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
if(bw.CancellationPending == true)
{
e.Cancel = true;
return;
}
e.Cancel = DoSVNCheckout(URL, e);
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
this.textBox2.Text = "Canceled!";
}
else{
this.textBox2.Text = "Done!";
}
}
您编写的代码存在许多问题。在我看来,两个主要问题是:
- 首先,您似乎将
BackgroundWorker
操作与您已经启动的单独 运行 过程混淆了。两者绝不相同,甚至互不相关。取消BackgroundWorker
不会对您启动的进程产生任何直接影响。您的问题不清楚这里实际需要的行为是什么,但是您没有做任何事情来实际终止外部进程。充其量,如果您不阻止等待进程产生输出的DoWork
方法,您将放弃该进程。实际上,如果不终止进程,您的DoWork
永远不会注意到您已尝试取消它,因为它卡在ReadLine()
调用上。 - 您正在以串行方式使用
StandardOutput
和StandardError
流,即一个接一个地使用。文档明确警告不要这样做,因为这是使代码死锁的一种非常可靠的方法。每个流的缓冲区相对较小,如果在缓冲区已满时尝试写入这些流之一,整个外部进程将挂起。这将反过来导致不再将输出写入任何流。在您的代码示例中,如果StandardError
流缓冲区在您能够完全读取StandardOutput
流之前变满,则外部进程将挂起,您自己的进程也会挂起。
另一个小问题是您没有利用 BackgroundWorker.ProgressChanged
事件,您可以使用该事件将从输出和错误字符串中读取的文本传递回 UI 线程,您可以在其中将该文本添加到文本框。这里使用 Control.Invoke()
并不是绝对必要的,并且不能充分利用 BackgroundWorker
.
有多种方法可以修改您编写的代码,这样您仍然可以使用 BackgroundWorker
并实现您的目标。您可以做出的一项明显改进是将 Process
对象引用存储在实例字段中,以便 StopButton_Click()
方法可以访问它。在该方法中,您可以调用 Process.Kill()
方法来实际终止进程。
但即便如此,您仍需要修复现有的容易发生死锁的实现。这可以通过多种方式完成:使用 Process.OutputDataReceived
和 Process.ErrorDataReceived
事件;创建第二个 BackgroundWorker
任务来处理其中一个流;使用基于 Task
的习语来阅读流。
最后一个选项是我的偏好。第二个选项不必要地创建 long-运行 线程,而基于事件的模式(第一个选项)使用起来很尴尬(并且是基于行的,因此在处理期间写入部分行的进程时价值有限他们的操作过程)。但是如果你打算使用基于 Task
的习语来读取流,在我看来你应该升级整个实现来这样做。
BackgroundWorker
仍然是可行的 class,但是新的 Task
功能与 async
/await
相结合关键字为恕我直言提供了一种更简单、更清晰的处理异步操作的方法。最大的优点之一是它不依赖于显式使用的线程(例如 运行 线程池线程中的 DoWork
事件处理程序)。异步 I/O 操作,例如构成整个场景的内容,通过 API 隐式处理,允许您编写的所有代码在您所在的 UI 线程中执行想要。
这是您的示例的一个版本:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private TaskCompletionSource<bool> _cancelTask;
private async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
button2.Enabled = true;
_cancelTask = new TaskCompletionSource<bool>();
try
{
await RunProcess();
}
catch (TaskCanceledException)
{
MessageBox.Show("The operation was cancelled");
}
finally
{
_cancelTask = null;
button1.Enabled = true;
button2.Enabled = false;
}
}
private void button2_Click(object sender, EventArgs e)
{
_cancelTask.SetCanceled();
}
private async Task RunProcess()
{
Process process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/C pause",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = false,
}
};
process.Start();
Task readerTasks = Task.WhenAll(
ConsumeReader(process.StandardError),
ConsumeReader(process.StandardOutput));
Task completedTask = await Task.WhenAny(readerTasks, _cancelTask.Task);
if (completedTask == _cancelTask.Task)
{
process.Kill();
await readerTasks;
throw new TaskCanceledException(_cancelTask.Task);
}
}
private async Task ConsumeReader(TextReader reader)
{
char[] text = new char[512];
int cch;
while ((cch = await reader.ReadAsync(text, 0, text.Length)) > 0)
{
textBox1.AppendText(new string(text, 0, cch));
}
}
}
备注:
- 首先,如您所见,根本不再需要
BackgroundWorker
。async
/await
模式隐含地完成了BackgroundWorker
为您完成的所有相同工作,但没有设置和管理它所需的所有额外样板代码。 - 有一个新的实例字段,
_cancelTask
,代表一个可以完成的简单Task
对象。它只能通过被取消来完成,在这种情况下,但这并不是严格要求的……您会注意到监视任务完成的await
语句实际上并不关心任务是如何结束的。就这样。在更复杂的场景中,对于这样一个Task
对象,可能实际上想要使用Result
,调用SetResult()
来完成任务,并使用SetCanceled()
实际上取消所代表的操作。这一切都取决于具体的上下文。 button1_Click()
方法(等同于你的Submit_Click()
方法)写得好像一切都是同步发生的。通过await
语句的"magic",该方法实际上分两部分执行。单击该按钮时,将执行await
语句之前的所有内容;在await
,一旦RunProcess()
方法 returned,button1_Click()
方法 returns。它将在稍后恢复执行,当RunProcess()
编辑的Task
对象 return 完成时,这反过来会在该方法结束时发生(即 not 第一次 returns).- 在
button1_Click()
方法中,更新 UI 以反映当前的操作状态:开始按钮已禁用,取消按钮已启用。在 returning 之前,按钮会 returned 到它们的原始状态。 button1_Click()
方法也是创建并随后丢弃_cancelTask
对象的地方。如果RunProcess()
抛出,await RunProcess()
语句将看到TaskCanceledException
;这用于向用户显示MessageBox
报告操作已被取消。您当然可以按照您认为合适的方式响应此类异常。- 这样,
button2_Click()
方法(相当于你的StopButton_Click()
方法)只需将_cancelTask
对象设置为完成状态,在本例中通过调用SetCanceled()
. RunProcess()
方法是进程的主要处理发生的地方。它启动进程,然后等待相关任务完成。表示输出流和错误流的两个任务包装在对Task.WhenAll()
的调用中。这将创建一个新的Task
对象,该对象仅在所有包装任务完成时才会完成。然后该方法通过Task.WhenAny()
等待包装器任务和_cancelTask
对象。如果或完成,该方法将完成执行。如果完成的任务是_cancelTask
对象,该方法通过终止启动的进程(在它正在做的事情中中断它),等待进程实际退出(可以通过包装器任务的完成......当输出流和错误流都到达末尾时,这些完成,这又发生在进程退出时),然后抛出TaskCanceledException
.ConsumeReader()
方法是一种辅助方法,它简单地从给定的TextReader
对象中读取文本,并将输出附加到文本框。它使用TextReader.ReadAsync()
;这种类型的方法也可以使用TextReader.ReadLineAsync()
来编写,但在那种情况下,您只会在每行的末尾看到输出。使用ReadAsync()
可确保在输出可用时立即检索输出,而无需等待换行符。- 请注意,
RunProcess()
和ConsumeReader()
方法也是async
,并且其中也有await
语句。与button1_Click()
一样,这些方法最初 return 在到达await
语句时执行,稍后在等待的Task
完成时恢复执行。在ConsumeReader()
示例中,您还会注意到await
解压了int
值,即Task<int>
的Result
属性 值正在等待。await
语句构成一个表达式,计算结果为等待的Task
的Result
值。 - 在每种情况下使用
await
的一个非常重要的特征是框架在 UI 线程 上恢复方法 的执行.这就是为什么button1_Click()
方法在await
之后仍然可以访问UI对象button1
和button2
,以及为什么ConsumeReader()
可以访问textBox1
对象,目的是每次通过ReadAsync()
方法 return 编辑时附加文本。
我意识到以上内容可能需要消化很多。特别是当其中大部分涉及从使用 BackgroundWorker
到基于 Task
的 API 的完全改变时,而不是解决我一开始提到的主要两个问题。但我希望您能看到这些更改是如何隐含地解决这些问题的,以及如何通过使用现代 async
/[=36 以更简单、更易于阅读的方式满足代码的其他要求=] 模式。
为了完整起见,这里是与上述 Form1
class:
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(12, 12);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "Start";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Enabled = false;
this.button2.Location = new System.Drawing.Point(93, 12);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 0;
this.button2.Text = "Stop";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// textBox1
//
this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBox1.Location = new System.Drawing.Point(13, 42);
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.ReadOnly = true;
this.textBox1.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.textBox1.Size = new System.Drawing.Size(488, 258);
this.textBox1.TabIndex = 1;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(513, 312);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.TextBox textBox1;
}