异步等待很少有混淆
Async Await Few Confusions
在体验 4.5 的新异步和等待功能时,我想在继续之前清除一些困惑。我一直在阅读不同的文章以及关于 SO 的不同问题,它帮助我理解 Async 和 Await 的工作原理。我将尝试将我的理解和困惑放在这里,如果有人编码教育我和其他正在寻找相同东西的人,我将不胜感激。我正在用非常简单的措辞讨论这个问题。
所以使用了Async,让编译器知道Async标记的方法中包含Await操作(Long操作)。最新框架包含用于异步操作的不同新内置方法。
像connection.OpenAsync、ExecuteScalarAsync 等内置异步函数与 Await 关键字一起使用。我不知道这些异步方法的内部工作原理,但我强烈猜测它们在幕后使用的是任务。
我能否将此作为一般规则,即 Await 将与任何实现 Task 的方法一起使用。因此,如果我需要创建自己的执行长时间操作的方法,那么我会将其创建为 Task 并在调用时使用 Await 关键字吗?
第二个最重要的事情是将方法创建为 Async 或将其创建为任务的经验法则是什么。例如,
public void SampleMain()
{
for (int i = 1; i <= 100; i++)
{
DataTable dt = ReadData(int id);
}
}
public DataTable ReadData(int id)
{
DataTable resultDT = new DataTable();
DataTable dt1 = new DataTable();
// Do Operation to Fill DataTable from first connection string
adapter.Fill(dt1);
DataTable dt2 = new DataTable();
// Do Operation to Fill DataTable from first connection string
adapter.Fill(dt2);
// Code for combining datatable and returning the resulting datatable
// Combine DataTables
return resultDT;
}
public string GetPrimaryConnectionString()
{
// Retrieve connection string from some file io operations
return "some primary connection string";
}
public string GetSecondaryConnectionString()
{
// Retrieve connection string from some file io operations
return "some secondaryconnection string";
}
现在这是一个非常简单的场景,它是我根据我过去工作过的一些真实世界的应用程序创建的。所以我只是想知道如何使整个过程异步。
我是否应该将 GetPrimaryConnectionString 和 GetSecondaryConnectionString 作为任务并在 ReadData 中等待它们。 ReadData 也会是一个任务吗? SampleMain函数中如何调用ReadData?
另一种方法是在 SampleMain 中为 ReadData 创建一个任务,运行 该任务并跳过将其他方法转换为任务。这是好方法吗?它会是真正的异步吗?
So Async is used so that compiler know that method marked by Async
contains Await operation
async
用于让编译器指示从该方法创建状态机。 async
方法可以没有 await
,并且仍然有效,尽管它将完全同步执行。
The builtin Async functions like connection.OpenAsync,
ExecuteScalarAsync etc are used with Await keyword. I don't know the
inner working of these Async Methods but my strong guess is that under
the hood they are using Tasks.
Task
是对未来要完成的工作的承诺。有几种方法可以创建 Task
。但是,Task
并不是唯一可以表示异步操作的东西。如果你愿意,你可以自己创建一个 awaitable,它只需要它来实现一个 GetAwaiter
方法,该方法 returns 一个实现 INotifyCompletion
的类型。
如果你想知道一个方法在框架中是如何实现的,you can view the source。在这种特殊情况下,他们使用 TaskCompletionSource<T>
.
Should I make GetPrimaryConnectionString and
GetSecondaryConnectionString as Tasks and Await them in ReadData. Will
ReadData be also a Task? How to call ReadData in the SampleMain
function?
检索连接字符串本质上并不异步。您通常(并非总是)将 async-await
用于自然异步 IO 操作。在这种特殊情况下,唯一实际的异步操作是 ReadData
,如果你想让它异步,你可以使用 SqlDataReader
,它公开了异步方法。
一个例子,取自 ADO.NET teams blog:
public static async Task<Product> GetProductAndReviewsAsync(
int productID, int reviewsToGet)
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
await connection.OpenAsync();
const string commandString = GetProductByIdCommand + ";"
+ GetProductReviewsPagedById;
using (SqlCommand command = new SqlCommand(commandString, connection))
{
command.Parameters.AddWithValue("productid", productID);
command.Parameters.AddWithValue("reviewStart", 0);
command.Parameters.AddWithValue("reviewCount", reviewsToGet);
using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
if (await reader.ReadAsync())
{
Product product = GetProductFromReader(reader, productID);
if (await reader.NextResultAsync())
{
List<Review> allReviews = new List<Review>();
while (await reader.ReadAsync())
{
Review review = GetReviewFromReader(reader);
allReviews.Add(review);
}
product.Reviews = allReviews.AsReadOnly();
return product;
}
else
{
throw new InvalidOperationException(
"Query to server failed to return list of reviews");
}
}
else
{
return null;
}
}
}
}
}
how to make this whole process Async
如果 adapter.Fill
有异步版本,那么在 ReadData
中为它简单地 await
,它又变成 async
,你可以在中等待它调用方方法:
// in async button click event
button.Enabled = false;
var dt = await ReadData(int id);
button.Enabled = true;
... // do something with dt
public async Task<DataTable> ReadData(int id)
{
...
var job1 = adapter.AsyncFill(dt1);
var job2 = adapter.Fill(dt2);
// wait for all of them to finish
Task.WaitAll(new[] {job1, job2});
...
return Task.FromResult(resultDT); // dump approach
}
如果没有异步版本那么你必须创建它们(使用Task
):
// in async button click event
button.Enabled = false;
// run synchronous task asynchronously
var dt = await Task.Run(() => ReadData(int id));
button.Enabled = true;
... // do something with dt
async/await
在遇到UI的时候闪闪发光,否则(如果没有UI参与)就在那里创建任务和运行同步操作。
使用 async-await 的唯一原因是你的主线程可能会做一些有用的事情,而另一个线程正在做长度操作。如果主线程启动另一个线程只等待另一个线程完成,最好让主线程执行操作。
主线程经常做的事情之一是保持 UI 响应。
你是对的,在后台 async-await 使用任务,因此你看到异步函数 return 是一个任务。
规则:
- 如果函数 return 无效,则异步版本 returns 任务。如果函数 return TResult,异步版本应该 return Task
<TResult
>.
- 有一个例外:异步事件处理程序return无效。
- 等待任务的return值无效。 await Task
<TResult
> 的 return 值为 TResult.
- 只有异步函数可以调用其他异步函数。
- 如果您有非异步函数,您仍然可以使用异步函数。但是你不能使用等待。使用异步函数的任务return值和System.Threading.Tasks.Task方法等待结果。
如果你有一个异步函数并且想在一个单独的线程中启动一个非异步函数,使用:
private int SlowCalculation(int a, int b)
{
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
return a + b;
}
私有异步任务 CalculateAsync(int a, int b)
{
任务 myTask = Task.Run( () => SlowCalculation(a, b);
// 当 SlowCalcuation 计算缓慢时,做其他有用的事情
// 过一会儿你需要答案
int sum = await myTask;
return总和;
}
看到await Task<int
>的return是int.
有些人曾经使用 Task.ContinueWith 这样的函数。因为不再需要 await 语句。等待确保任务完成。 await 之后的语句是您通常在 ContinueWith 中执行的操作。
在Task.ContinueWith中你可以说:"do this only if the task failed"。 async-await 等价于 try-catch。
Remember: if your thread has nothing useful to do (like keeping your UI responsive), don't use async-await
在 async-await 中启动多个任务并等待它们完成,如下所示:
private async Task MyAsyncFunction(...)
{
var tasks = new List<Task<int>>();
for (int i=0; i<10; ++i)
{
tasks.Add(CalculateAsync(i, 2*i);
}
// while all ten tasks are slowly calculating do something useful
// after a while you need the answer, await for all tasks to complete:
await Task.WhenAll(tasks);
// the result is in Task.Result:
if (task[3].Result < 5) {...}
}
The async-await version of Task.Waitall is Task.WhenAll. WhenAll returns a Task instead of void, so you can await for it. The main thread remains responsive even while awaiting.
使用Task.WaitAll时主线程不是这样,因为你没有await
在体验 4.5 的新异步和等待功能时,我想在继续之前清除一些困惑。我一直在阅读不同的文章以及关于 SO 的不同问题,它帮助我理解 Async 和 Await 的工作原理。我将尝试将我的理解和困惑放在这里,如果有人编码教育我和其他正在寻找相同东西的人,我将不胜感激。我正在用非常简单的措辞讨论这个问题。
所以使用了Async,让编译器知道Async标记的方法中包含Await操作(Long操作)。最新框架包含用于异步操作的不同新内置方法。
像connection.OpenAsync、ExecuteScalarAsync 等内置异步函数与 Await 关键字一起使用。我不知道这些异步方法的内部工作原理,但我强烈猜测它们在幕后使用的是任务。
我能否将此作为一般规则,即 Await 将与任何实现 Task 的方法一起使用。因此,如果我需要创建自己的执行长时间操作的方法,那么我会将其创建为 Task 并在调用时使用 Await 关键字吗?
第二个最重要的事情是将方法创建为 Async 或将其创建为任务的经验法则是什么。例如,
public void SampleMain()
{
for (int i = 1; i <= 100; i++)
{
DataTable dt = ReadData(int id);
}
}
public DataTable ReadData(int id)
{
DataTable resultDT = new DataTable();
DataTable dt1 = new DataTable();
// Do Operation to Fill DataTable from first connection string
adapter.Fill(dt1);
DataTable dt2 = new DataTable();
// Do Operation to Fill DataTable from first connection string
adapter.Fill(dt2);
// Code for combining datatable and returning the resulting datatable
// Combine DataTables
return resultDT;
}
public string GetPrimaryConnectionString()
{
// Retrieve connection string from some file io operations
return "some primary connection string";
}
public string GetSecondaryConnectionString()
{
// Retrieve connection string from some file io operations
return "some secondaryconnection string";
}
现在这是一个非常简单的场景,它是我根据我过去工作过的一些真实世界的应用程序创建的。所以我只是想知道如何使整个过程异步。
我是否应该将 GetPrimaryConnectionString 和 GetSecondaryConnectionString 作为任务并在 ReadData 中等待它们。 ReadData 也会是一个任务吗? SampleMain函数中如何调用ReadData?
另一种方法是在 SampleMain 中为 ReadData 创建一个任务,运行 该任务并跳过将其他方法转换为任务。这是好方法吗?它会是真正的异步吗?
So Async is used so that compiler know that method marked by Async contains Await operation
async
用于让编译器指示从该方法创建状态机。 async
方法可以没有 await
,并且仍然有效,尽管它将完全同步执行。
The builtin Async functions like connection.OpenAsync, ExecuteScalarAsync etc are used with Await keyword. I don't know the inner working of these Async Methods but my strong guess is that under the hood they are using Tasks.
Task
是对未来要完成的工作的承诺。有几种方法可以创建 Task
。但是,Task
并不是唯一可以表示异步操作的东西。如果你愿意,你可以自己创建一个 awaitable,它只需要它来实现一个 GetAwaiter
方法,该方法 returns 一个实现 INotifyCompletion
的类型。
如果你想知道一个方法在框架中是如何实现的,you can view the source。在这种特殊情况下,他们使用 TaskCompletionSource<T>
.
Should I make GetPrimaryConnectionString and GetSecondaryConnectionString as Tasks and Await them in ReadData. Will ReadData be also a Task? How to call ReadData in the SampleMain function?
检索连接字符串本质上并不异步。您通常(并非总是)将 async-await
用于自然异步 IO 操作。在这种特殊情况下,唯一实际的异步操作是 ReadData
,如果你想让它异步,你可以使用 SqlDataReader
,它公开了异步方法。
一个例子,取自 ADO.NET teams blog:
public static async Task<Product> GetProductAndReviewsAsync(
int productID, int reviewsToGet)
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
await connection.OpenAsync();
const string commandString = GetProductByIdCommand + ";"
+ GetProductReviewsPagedById;
using (SqlCommand command = new SqlCommand(commandString, connection))
{
command.Parameters.AddWithValue("productid", productID);
command.Parameters.AddWithValue("reviewStart", 0);
command.Parameters.AddWithValue("reviewCount", reviewsToGet);
using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
if (await reader.ReadAsync())
{
Product product = GetProductFromReader(reader, productID);
if (await reader.NextResultAsync())
{
List<Review> allReviews = new List<Review>();
while (await reader.ReadAsync())
{
Review review = GetReviewFromReader(reader);
allReviews.Add(review);
}
product.Reviews = allReviews.AsReadOnly();
return product;
}
else
{
throw new InvalidOperationException(
"Query to server failed to return list of reviews");
}
}
else
{
return null;
}
}
}
}
}
how to make this whole process Async
如果 adapter.Fill
有异步版本,那么在 ReadData
中为它简单地 await
,它又变成 async
,你可以在中等待它调用方方法:
// in async button click event
button.Enabled = false;
var dt = await ReadData(int id);
button.Enabled = true;
... // do something with dt
public async Task<DataTable> ReadData(int id)
{
...
var job1 = adapter.AsyncFill(dt1);
var job2 = adapter.Fill(dt2);
// wait for all of them to finish
Task.WaitAll(new[] {job1, job2});
...
return Task.FromResult(resultDT); // dump approach
}
如果没有异步版本那么你必须创建它们(使用Task
):
// in async button click event
button.Enabled = false;
// run synchronous task asynchronously
var dt = await Task.Run(() => ReadData(int id));
button.Enabled = true;
... // do something with dt
async/await
在遇到UI的时候闪闪发光,否则(如果没有UI参与)就在那里创建任务和运行同步操作。
使用 async-await 的唯一原因是你的主线程可能会做一些有用的事情,而另一个线程正在做长度操作。如果主线程启动另一个线程只等待另一个线程完成,最好让主线程执行操作。
主线程经常做的事情之一是保持 UI 响应。
你是对的,在后台 async-await 使用任务,因此你看到异步函数 return 是一个任务。
规则:
- 如果函数 return 无效,则异步版本 returns 任务。如果函数 return TResult,异步版本应该 return Task
<TResult
>. - 有一个例外:异步事件处理程序return无效。
- 等待任务的return值无效。 await Task
<TResult
> 的 return 值为 TResult. - 只有异步函数可以调用其他异步函数。
- 如果您有非异步函数,您仍然可以使用异步函数。但是你不能使用等待。使用异步函数的任务return值和System.Threading.Tasks.Task方法等待结果。
如果你有一个异步函数并且想在一个单独的线程中启动一个非异步函数,使用:
private int SlowCalculation(int a, int b) { System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5)); return a + b; }
私有异步任务 CalculateAsync(int a, int b) { 任务 myTask = Task.Run( () => SlowCalculation(a, b); // 当 SlowCalcuation 计算缓慢时,做其他有用的事情 // 过一会儿你需要答案 int sum = await myTask; return总和; }
看到await Task<int
>的return是int.
有些人曾经使用 Task.ContinueWith 这样的函数。因为不再需要 await 语句。等待确保任务完成。 await 之后的语句是您通常在 ContinueWith 中执行的操作。
在Task.ContinueWith中你可以说:"do this only if the task failed"。 async-await 等价于 try-catch。
Remember: if your thread has nothing useful to do (like keeping your UI responsive), don't use async-await
在 async-await 中启动多个任务并等待它们完成,如下所示:
private async Task MyAsyncFunction(...)
{
var tasks = new List<Task<int>>();
for (int i=0; i<10; ++i)
{
tasks.Add(CalculateAsync(i, 2*i);
}
// while all ten tasks are slowly calculating do something useful
// after a while you need the answer, await for all tasks to complete:
await Task.WhenAll(tasks);
// the result is in Task.Result:
if (task[3].Result < 5) {...}
}
The async-await version of Task.Waitall is Task.WhenAll. WhenAll returns a Task instead of void, so you can await for it. The main thread remains responsive even while awaiting.
使用Task.WaitAll时主线程不是这样,因为你没有await