通过单击一个按钮,我需要异步调用函数f1()来启动一个长操作。当f1()运行时,我想用另一个按钮启动另一个函数f2()。当函数f1()完成时,我想认识到这一点。
我将整个过程分解为一个非常简单的示例,其中一个按钮启动一个长函数writeStars(),它每隔200毫秒就将“*”写到字符串上,总计20次。使用另一个按钮,我可以异步地将'-‘写入同一个字符串。
private async void btnStartThread1_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(writeStars);
thread1.Start();
}
private void btnWriteMinus_Click(object sender, EventArgs e)
{
_Str += "-";
}
async void writeStars()
{
int count = 20;
do
{
_Str += "*";
Thread.Sleep(200);
} while (count-- > 0);
}上面的代码给出了这样的结果:‘*-*。
或者更快地单击第二个按钮:‘**--*-*。
这是真正的异步:-)
我希望使用异步任务,以便能够等待writeStars()的末尾,然后maybee按钮。我试过了,但我没有办法用任务、异步和等待来编写这个简单的示例。
发布于 2020-01-29 20:22:30
您可能应该使用async和await。基本上,假设您没有主动阻止GUI,这两个函数(和两个按钮)之间没有任何关系。
例如,您可以使用4个控件创建一个简单的UI (我是在WPF中实现的)。两个按钮(每个按钮调用一个异步方法)和两个文本块(将在异步方法之后更新)。
WPF代码非常简单。注意单击事件的名称。
<UniformGrid Columns="2">
<Button x:Name="UpdateSlow" Content="Wait 10 seconds" Click="UpdateSlow_Click" />
<Button x:Name="UpdateFast" Content="Wait 3 seconds" Click="UpdateFast_Click" />
<TextBlock x:Name="LongWaitTime" Text="Waiting for text" />
<TextBlock x:Name="ShortWaitTime" Text="Waiting for text" />
</UniformGrid>以及后面的代码,其中包含单击事件。
private async void UpdateSlow_Click(object sender, RoutedEventArgs e)
{
await Task.Delay(10000);
LongWaitTime.Text = "I finished in 10 seconds";
}
private async void UpdateFast_Click(object sender, RoutedEventArgs e)
{
await Task.Delay(3000);
ShortWaitTime.Text = "I finished in 3 seconds";
}如果您单击"UpdateSlow“按钮,在更改"LongWaitTime”文本块之前,会有10秒的延迟。同时,您可以单击"UpdateFast“按钮,它将在3秒后更新"ShortWaitTime”文本块。最有可能在10秒等待时间结束之前完成(当然,这取决于您单击的时间)。
希望这有意义..。
修改文本块:
我不知道你的目标是什么,这让我很难找到一个真正好的解决方案。如果只是为了好玩,下面的代码应该可以工作。但是,我不确定从两种不同的方法访问相同的字符串构建是否会产生任何副作用,所以我不建议在生产中使用这种方法。
<UniformGrid Columns="1">
<Button x:Name="AddStar" Content="Add star to text" Click="AddStar_Click" />
<TextBlock x:Name="Dots" Text="." Margin="20 0" VerticalAlignment="Center" FontSize="24" />
</UniformGrid>
public MainWindow()
{
InitializeComponent();
PrintDots();
}
public async void PrintDots()
{
// This will run forever, printing a dot every 0.2 seconds, clearing the string, when it reaches 50 characters.
while (true)
{
_sb.Append(".");
await Task.Delay(200);
if (_sb.Length == 50) { _sb.Clear(); }
Dots.Text = _sb.ToString();
}
}
private void AddStar_Click(object sender, RoutedEventArgs e)
{
// On click a star (asterix) will be appended to the string
_sb.Append("*");
Dots.Text = _sb.ToString();
}我决定将字符串保留在StringBuilder中,因为这对性能来说应该要好得多,并通过使用“追加”方法来提高可读性。
公平警告:
通常,async void只应在用户界面中使用(例如在按钮单击事件上)。在大多数其他情况下,这不是异步编程的好方法。一旦启动,程序将无法跟踪该方法的进度。这可能会导致各种奇怪的情况,在这种情况下,您不知道哪些方法是完成的,哪些方法不知道。相反,您应该使用可以返回和等待的async Task。
发布于 2020-01-29 20:26:22
我有点不清楚您想要什么--您一开始说要在f2运行时启动f1,但接着您要求等待f1的结束,并启用f2按钮。我将回答第二种情况,在f1完成之前,您不希望运行f1。
您可以使用这样的设计(借口、语法或格式怪异,我已经很长时间没有在C#工作了):
async void btnWriteStars_Click() {
try {
btnWriteMinus.Enabled = false;
await writeStars();
} finally {
btnWriteMinus.Enabled = true;
}
}
Task WriteStars() {
return Task.Run(() => {
// much stringy goodness here
});
}策略是“写我星”事件处理程序禁用不想中断异步处理的按钮,然后等待异步处理完成,然后再次启用该按钮。
注意,仅仅完成WriteStars异步是不够的--它仍然会在调用线程上运行,直到第一个await,当然您的WriteStars没有await。因此,我们使用Task.Run或类似的函数生成异步任务,其中运行同步代码块。根据您的实际方法所做的,您可能可以将其编写为async,而不是显式地干预任务工厂--在任何情况下,关键是WriteStars返回一个任务对象以供按钮事件处理程序等待。
如果您的需求更复杂--就像您想让button2启用,但在编写星号完成之后,将其“追加减号”操作排队--那么您的设计也需要更加复杂。您可能可以将Task实例保存在表单级别,并让button2在其上执行ContinueWith,但是对于真正复杂的场景,您可能需要实现生产者-使用者方法,其中UI按钮将操作放在队列上,而工作线程则将它们取下来。但是对于简单的“Y正在运行时不要做X”的场景,禁用-等待-启用应该足够了。
https://stackoverflow.com/questions/59974766
复制相似问题