不用说,我是 C# 新手,并且在此过程中做了一些基本的事情来学习。我试图理解异步/等待功能。我有一个带有 2 个文本框和一个按钮的表单。当我单击该按钮时,它会检查远程计算机的服务状态(在本例中为远程注册表)。在检查时,它会写入正在检查注册表并告诉用户等待。然后,如果状态停止并获取一些值,则启动我查找并将它们写入第二个文本框。
我的代码有效,并且我能够获取我的值。但 GUI 在此期间冻结。我尝试实现后台工作程序、线程和异步/等待,但没有成功。我尝试执行任务但无法使其正常工作。
这里或其他网站上显示的所有示例都只是返回 int,而我试图返回一个将状态和我想要的值写入文本框的字符串。我尝试将其转换为字符串,但没有成功。
长话短说,对于这种类型的流程,更好的方法是什么?有人可以告诉我如何做并评论什么是做什么,以便我更好地理解吗?我想学习如何做到这一点,但同时了解原因并学习编写更简洁的代码。
感谢大家提前抽出时间。
string ComputerName = "Lab01";
public frmRegChecker()
{
InitializeComponent();
}
private void Button1_Click(object sender, EventArgs e)
{
Check_Status();
}
private void Check_Status()
{
TxtBoxstatus.AppendText("Checking Remote Registry service status on computer : " + ComputerName);
TxtBoxstatus.AppendText(Environment.NewLine);
TxtBoxstatus.AppendText("Please wait... ");
ServiceController sc = new ServiceController("RemoteRegistry", ComputerName);
try
{
TxtBoxstatus.AppendText("The Remote Registry service status is currently set to : " + sc.Status.ToString());
TxtBoxstatus.AppendText(Environment.NewLine);
if (sc.Status == ServiceControllerStatus.Stopped)
{
// Start the service if the current status is stopped.
TxtBoxstatus.AppendText("Starting Remote Registry service...");
TxtBoxstatus.AppendText(Environment.NewLine);
try
{
// Start the service, and wait until its status is "Running".
sc.Start();
sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 3));
sc.Refresh();
sc.WaitForStatus(ServiceControllerStatus.Running);
// Display the current service status.
TxtBoxstatus.AppendText("The Remote Registry status is now set to:" + sc.Status.ToString());
richTextBox1.AppendText(Environment.NewLine);
try
{
var reg = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ComputerName);
var key = reg.OpenSubKey(@"Software\Microsoft\Windows NT\CurrentVersion\");
string _OSVersion = (key.GetValue("CurrentVersion")).ToString();
richTextBox1.AppendText("OS version is : " + _OSVersion);
richTextBox1.AppendText(Environment.NewLine);
}
catch (InvalidOperationException)
{
richTextBox1.AppendText("Error getting registry value from" + ComputerName);
richTextBox1.AppendText(Environment.NewLine);
}
}
catch (InvalidOperationException)
{
richTextBox1.AppendText("Could not start the Remote Registry service.");
richTextBox1.AppendText(Environment.NewLine);
}
}
else if (sc.Status == ServiceControllerStatus.Running)
{
try
{
var reg = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ComputerName);
var key = reg.OpenSubKey(@"Software\Microsoft\Windows NT\CurrentVersion\");
string _OSVersion = (key.GetValue("CurrentVersion")).ToString();
richTextBox1.AppendText("OS version is : " + _OSVersion);
richTextBox1.AppendText(Environment.NewLine);
}
catch (InvalidOperationException)
{
richTextBox1.AppendText("Error getting registry value from" + ComputerName);
richTextBox1.AppendText(Environment.NewLine);
}
}
}
}
catch
{
richTextBox1.AppendText("Error getting registry value from " + ComputerName);
richTextBox1.AppendText(Environment.NewLine);
}
}
不幸的是,
ServiceController
是一个相当过时的类,并且本身不支持async
/await
。如果可能的话,这将是最干净的解决方案。
因此,您可以将
async
/await
与 Task.Run
一起使用。 Task.Run
在后台线程上执行代码,您可以使用 UI 中的 async
/await
来使用该后台操作。这种方法允许以自然的方式传播和处理异常,并且它还允许自然地处理返回值。为了简单起见,现在删除文本框更新,第一步如下所示:
private async void Button1_Click(object sender, EventArgs e)
{
try
{
var osVersion = await Task.Run(() => CheckStatus());
richTextBox1.AppendText("OS version is : " + osVersion);
richTextBox1.AppendText(Environment.NewLine);
}
catch (InvalidOperationException ex)
{
richTextBox1.AppendText("Error getting registry value from" + ComputerName);
richTextBox1.AppendText(ex.ToString());
richTextBox1.AppendText(Environment.NewLine);
}
}
private string CheckStatus()
{
ServiceController sc = new ServiceController("RemoteRegistry", ComputerName);
if (sc.Status == ServiceControllerStatus.Stopped)
{
// Start the service if the current status is stopped.
// Start the service, and wait until its status is "Running".
sc.Start();
sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 3));
sc.Refresh();
sc.WaitForStatus(ServiceControllerStatus.Running);
}
var reg = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ComputerName);
var key = reg.OpenSubKey(@"Software\Microsoft\Windows NT\CurrentVersion\");
return (key.GetValue("CurrentVersion")).ToString();
}
接下来,添加进度更新。有一个内置机制 -
IProgress<T>
/Progress<T>
,它的工作原理如下:
private async void Button1_Click(object sender, EventArgs e)
{
try
{
var progress = new Progress<string>(update =>
{
TxtBoxstatus.AppendText(update);
TxtBoxstatus.AppendText(Environment.NewLine);
});
var osVersion = await Task.Run(() => CheckStatus(progress));
richTextBox1.AppendText("OS version is : " + osVersion);
richTextBox1.AppendText(Environment.NewLine);
}
catch (InvalidOperationException ex)
{
richTextBox1.AppendText("Error getting registry value from" + ComputerName);
richTextBox1.AppendText(ex.ToString());
richTextBox1.AppendText(Environment.NewLine);
}
}
private string CheckStatus(IProgres<string> progress)
{
progress?.Report("Checking Remote Registry service status on computer : " + ComputerName);
progress?.Report("Please wait... ");
ServiceController sc = new ServiceController("RemoteRegistry", ComputerName);
progress?.Report("The Remote Registry service status is currently set to : " + sc.Status.ToString());
if (sc.Status == ServiceControllerStatus.Stopped)
{
// Start the service if the current status is stopped.
progress?.Report("Starting Remote Registry service...");
// Start the service, and wait until its status is "Running".
sc.Start();
sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 3));
sc.Refresh();
sc.WaitForStatus(ServiceControllerStatus.Running);
progress?.Report("The Remote Registry status is now set to:" + sc.Status.ToString());
}
var reg = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ComputerName);
var key = reg.OpenSubKey(@"Software\Microsoft\Windows NT\CurrentVersion\");
return (key.GetValue("CurrentVersion")).ToString();
}
注意关注点分离:接触 UI 对象的唯一代码是 UI 事件处理程序。包含实际程序逻辑的
CheckStatus
方法与 UI 分离 - 它只知道它可以报告进度字符串并返回字符串结果。
这可以使用 async/await 轻松完成。一个例子:
一个简单的 WPF 表单:
<Window x:Class="WpfApp1.MainWindow"
...>
<Grid>
<Button Name="button" Content="Start" Click="Button_Click"/>
<TextBox Name="textButton" />
</Grid>
</Window>
以及相关的隐藏代码:
public partial class MainWindow:Window
{
public MainWindow()
{
InitializeComponent();
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
textButton.Text = "Running...";
string result = await DoIt();
textButton.Text = result;
}
private async Task<string> DoIt()
{
await Task.Delay(3000);
return "Finished...";
}
}
单击按钮时,“DoIt”中长时间运行的“计算”将“异步”启动。当它运行时,UI 保持响应。 当 DoIt 返回其结果时(在此示例中,经过 3 秒的虚拟延迟后),“单击”事件处理程序将继续...
备注:
为了简单起见,此示例使用隐藏代码。该技术也可以使用 MVVM 模式(其中异步操作从命令启动)。
“async void”是一种反模式,但用于遵守后面代码中自动生成的事件处理程序。
在现实世界的应用程序中,异步“DoIt”方法可能位于后端“模型类”中。
该示例假设您只想在长时间运行的操作完成后更新 UI。如果您想要中间更新,有几个选项(不幸的是有点复杂)。
我认为您仍然可以使用后台工作程序使文本框显示消息而不冻结 GUI。参考这个问题Here,了解如何从backgroundworker返回对象。
我总是喜欢按照以下方式对待任何具有“潜力”长期运行的函数(代码是 dotnet6.0 及更高版本):
一个表示数据源的简单类:
public static class RandomStringGenerator
{
private static char[] chars = new[]{'a','b','c','d', '1', '2', '3' };
/// Builds a random string.
public static string GenerateRandomString(int len)
{
var charArr = new char[len];
var rand = new Random();
var idxSpace = Unsafe.As<char[], ImmutableArray<char>>(ref charArr);
for(int i = 0; i < idxSpace.Length; i++)
charArr[i] = chars[rand.Next(0, chars.Length - 1)];
return new string(charArr);
}
/// Builds a random string array.
public static string[] GenerateRandomArray(int arrLen, int strLen)
{
var strArray = new string[arrLen];
var idxSpace = Unsafe.As<string[], ImmutableArray<string>>(ref strArray);
for (int i = 0; i < idxSpace.Length; i++)
strArray[i] = GenerateRandomString(strLen);
return strArray;
}
}
一个简单的类,模拟长时间运行的执行,当工作单元完成时通知 Progress 实例:
public class SimpleServiceClass
{
/// Our long running method that simulates calling report on the IProgress instance.
public void ReportOnStrings(string[] strings, IProgress<string> progress)
{
for (int i = 0; i < strings.Length; i++)
{
progress.Report(strings[i] + Environment.NewLine);
var x = 0;
while (x < (int.MaxValue / 2))
x++;
}
}
}
在调用长时间运行的方法时处理解锁 UX 的表单代码(在要附加文本框的代码中替换表单控件 ID):
/// Generic function which takes in the Progress<T>, the handler type to execute and the function that is going to execute.
public void ApplyUpdatesToElement<T>(Progress<T> progress, EventHandler<T> handler, Action<IProgress<T>> workExecution)
{
progress.ProgressChanged += handler;
workExecution(progress);
}
/// The async button click event handler.
private async void button2_Click(object sender, EventArgs e)
{
var progress = new Progress<string>();
var simpleServiceClass = new SimpleServiceClass();
var strings = await Task.Run(() => RandomStringGenerator.GenerateRandomArray(5, 8));
await Task.Run(() => ApplyUpdatesToElement(progress,
(sender, message) => textBox2.AppendText(message),
(pgs) => simpleServiceClass.ReportOnStrings(strings, pgs)));
}
此示例中的整个想法是展示 Process 类的 async/await 可以解除 UI 的阻塞,以便您可以使用它或以与非同一表单上的其他现有表单控件相同的方式进行类似的长时间运行的调用-阻止行为。