C# 异步函数过早返回
C# Async Function returning prematurely
我正在尝试等待异步函数完成,以便我可以在 UI 线程中填充 ListView。
这是代码
public Form1()
{
InitializeComponent();
Task t = new Task(Repopulate);
t.Start();
// some other work here
t.Wait(); //completes prematurely
}
async void Repopulate()
{
var query = ParseObject.GetQuery("Offer");
IEnumerable<ParseObject> results = await query.FindAsync();
if (TitleList == null)
TitleList = new List<string>();
foreach (ParseObject po in results)
TitleList.Add(po.Get<string>("title"));
}
Form1() 中的 TitleList = null 因为 Repopulate() 尚未完成。因此我使用了 Wait()。但是,在功能完成之前等待 returns。
我做错了什么?
您需要将 Repopulate
方法的 return 类型更改为 return 表示异步操作的任务。
此外,您不应从表单构造函数执行异步操作,因为调用 Task.Wait
会导致您的 UI 线程阻塞(并且您的应用程序看起来没有响应)。相反,订阅它的 Form.Load
方法,并在那里执行异步操作,使用 await
关键字使事件处理程序保持异步。如果您不希望用户在异步操作完成之前与表单交互,请在构造函数中禁用表单并在 Load
处理程序结束时重新启用它。
private async void Form1_Load(object sender, EventArgs e)
{
Task t = Repopulate();
// If you want to run its synchronous part on the thread pool:
// Task t = Task.Run(() => Repopulate());
// some other work here
await t;
}
async Task Repopulate()
{
var query = ParseObject.GetQuery("Offer");
IEnumerable<ParseObject> results = await query.FindAsync();
if (TitleList == null)
TitleList = new List<string>();
foreach (ParseObject po in results)
TitleList.Add(po.Get<string>("title"));
}
更新:为了未来读者的利益,我正在将我的评论修改为答案:
Task.Wait
导致调用线程阻塞,直到任务完成。表单构造函数和事件处理程序,就其本质而言,运行 在 UI 线程上,因此在其中调用 Wait
将导致 UI 线程阻塞。另一方面,await
关键字将导致当前方法将控制权交还给调用者——在事件处理程序的情况下,这将允许 UI 线程继续处理事件。任务完成后,等待方法(事件处理程序)将在 UI 线程上继续执行。
Task.Wait
将始终阻塞调用线程,无论是从构造函数还是事件处理程序调用,因此应避免使用它,尤其是当 运行ning 在 UI 上时线。为此,C# 5 引入了 async
和 await
关键字;但是,它们仅在方法上受支持,在构造函数上不受支持。此限制是您需要将初始化代码从表单构造函数移至异步事件处理程序的主要原因。
至于Task.Wait()
return过早的原因: 在你原来的代码中,task t
代表你在表单中实例化的 Task
的执行构造函数。这个任务运行s Repopulate
;但是,一旦遇到第一个 await
语句,上述方法就会 return ,并以即发即弃的方式执行其其余逻辑。这就是使用 async void
的危险——您将不知道异步方法何时完成执行。 (出于这个原因,async void
应该只用于事件处理程序。)换句话说,t.Wait()
return 一旦 Repopulate
命中它的第一个 await
.
通过将 Repopulate
的签名更改为 async Task
,您现在将获得另一个代表其异步执行完成的任务,包括 query.FindAsync()
异步调用及其后的处理。当 Task.Run
作为参数传递异步操作 (Func<Task>
) 时,其 returned 任务将等待(解包)inner 任务。这就是为什么应该使用 Task.Run
而不是 Task.Start
或 Task.Factory.StartNew
.
我正在尝试等待异步函数完成,以便我可以在 UI 线程中填充 ListView。
这是代码
public Form1()
{
InitializeComponent();
Task t = new Task(Repopulate);
t.Start();
// some other work here
t.Wait(); //completes prematurely
}
async void Repopulate()
{
var query = ParseObject.GetQuery("Offer");
IEnumerable<ParseObject> results = await query.FindAsync();
if (TitleList == null)
TitleList = new List<string>();
foreach (ParseObject po in results)
TitleList.Add(po.Get<string>("title"));
}
Form1() 中的 TitleList = null 因为 Repopulate() 尚未完成。因此我使用了 Wait()。但是,在功能完成之前等待 returns。
我做错了什么?
您需要将 Repopulate
方法的 return 类型更改为 return 表示异步操作的任务。
此外,您不应从表单构造函数执行异步操作,因为调用 Task.Wait
会导致您的 UI 线程阻塞(并且您的应用程序看起来没有响应)。相反,订阅它的 Form.Load
方法,并在那里执行异步操作,使用 await
关键字使事件处理程序保持异步。如果您不希望用户在异步操作完成之前与表单交互,请在构造函数中禁用表单并在 Load
处理程序结束时重新启用它。
private async void Form1_Load(object sender, EventArgs e)
{
Task t = Repopulate();
// If you want to run its synchronous part on the thread pool:
// Task t = Task.Run(() => Repopulate());
// some other work here
await t;
}
async Task Repopulate()
{
var query = ParseObject.GetQuery("Offer");
IEnumerable<ParseObject> results = await query.FindAsync();
if (TitleList == null)
TitleList = new List<string>();
foreach (ParseObject po in results)
TitleList.Add(po.Get<string>("title"));
}
更新:为了未来读者的利益,我正在将我的评论修改为答案:
Task.Wait
导致调用线程阻塞,直到任务完成。表单构造函数和事件处理程序,就其本质而言,运行 在 UI 线程上,因此在其中调用 Wait
将导致 UI 线程阻塞。另一方面,await
关键字将导致当前方法将控制权交还给调用者——在事件处理程序的情况下,这将允许 UI 线程继续处理事件。任务完成后,等待方法(事件处理程序)将在 UI 线程上继续执行。
Task.Wait
将始终阻塞调用线程,无论是从构造函数还是事件处理程序调用,因此应避免使用它,尤其是当 运行ning 在 UI 上时线。为此,C# 5 引入了 async
和 await
关键字;但是,它们仅在方法上受支持,在构造函数上不受支持。此限制是您需要将初始化代码从表单构造函数移至异步事件处理程序的主要原因。
至于Task.Wait()
return过早的原因: 在你原来的代码中,task t
代表你在表单中实例化的 Task
的执行构造函数。这个任务运行s Repopulate
;但是,一旦遇到第一个 await
语句,上述方法就会 return ,并以即发即弃的方式执行其其余逻辑。这就是使用 async void
的危险——您将不知道异步方法何时完成执行。 (出于这个原因,async void
应该只用于事件处理程序。)换句话说,t.Wait()
return 一旦 Repopulate
命中它的第一个 await
.
通过将 Repopulate
的签名更改为 async Task
,您现在将获得另一个代表其异步执行完成的任务,包括 query.FindAsync()
异步调用及其后的处理。当 Task.Run
作为参数传递异步操作 (Func<Task>
) 时,其 returned 任务将等待(解包)inner 任务。这就是为什么应该使用 Task.Run
而不是 Task.Start
或 Task.Factory.StartNew
.