Bất kỳ lúc nào bạn cần thực hiện một hành động trên máy chủ từ xa, chương trình của bạn sẽ tạo ra yêu cầu, gửi yêu cầu, sau đó chờ phản hồi. Tôi sẽ sử dụng SaveChanges()
và SaveChangesAsync()
làm ví dụ nhưng điều tương tự cũng áp dụng cho Find()
vàFindAsync()
.
Giả sử bạn có một danh sách myList
hơn 100 mục mà bạn cần thêm vào cơ sở dữ liệu của mình. Để chèn điều đó, hàm của bạn sẽ trông giống như sau:
using(var context = new MyEDM())
{
context.MyTable.AddRange(myList);
context.SaveChanges();
}
Đầu tiên, bạn tạo một phiên bản MyEDM
, thêm danh sách myList
vào bảng MyTable
, sau đó gọi SaveChanges()
để duy trì các thay đổi đối với cơ sở dữ liệu. Nó hoạt động theo cách bạn muốn, các bản ghi được cam kết, nhưng chương trình của bạn không thể làm bất cứ điều gì khác cho đến khi cam kết kết thúc. Quá trình này có thể mất nhiều thời gian tùy thuộc vào những gì bạn đang cam kết. Nếu bạn đang thực hiện các thay đổi đối với các bản ghi, thực thể phải thực hiện những thay đổi đó cùng một lúc (tôi đã từng có một lần lưu mất 2 phút để cập nhật)!
Để giải quyết vấn đề này, bạn có thể làm một trong hai điều. Đầu tiên là bạn có thể khởi động một luồng mới để xử lý phần chèn. Trong khi điều này sẽ giải phóng chuỗi gọi để tiếp tục thực thi, bạn đã tạo một chuỗi mới chỉ cần ngồi đó và chờ đợi. Không cần chi phí cao đó, và đây là những gì async await
mô hình giải quyết.
Đối với các đối tác I / O, hãy await
nhanh chóng trở thành người bạn tốt nhất của bạn. Lấy phần mã từ trên xuống, chúng ta có thể sửa đổi nó thành:
using(var context = new MyEDM())
{
Console.WriteLine("Save Starting");
context.MyTable.AddRange(myList);
await context.SaveChangesAsync();
Console.WriteLine("Save Complete");
}
Đó là một thay đổi rất nhỏ, nhưng có những ảnh hưởng sâu sắc đến hiệu quả và hiệu suất của mã của bạn. Vậy điều gì xảy ra? Đầu mã giống nhau, bạn tạo một phiên bản của MyEDM
và thêm myList
vào MyTable
. Nhưng khi bạn gọi await context.SaveChangesAsync()
, việc thực thi mã trở lại hàm gọi! Vì vậy, trong khi bạn đang đợi tất cả các bản ghi đó cam kết, mã của bạn có thể tiếp tục thực thi. Giả sử hàm chứa đoạn mã trên có chữ ký của public async Task SaveRecords(List<MyTable> saveList)
, hàm gọi có thể giống như sau:
public async Task MyCallingFunction()
{
Console.WriteLine("Function Starting");
Task saveTask = SaveRecords(GenerateNewRecords());
for(int i = 0; i < 1000; i++){
Console.WriteLine("Continuing to execute!");
}
await saveTask;
Console.Log("Function Complete");
}
Tại sao bạn lại có một chức năng như thế này, tôi không biết, nhưng những gì nó xuất ra cho thấy cách async await
hoạt động. Đầu tiên chúng ta hãy xem xét những gì sẽ xảy ra.
Việc thực thi đi vào MyCallingFunction
, Function Starting
sau đó Save Starting
được ghi vào bảng điều khiển, sau đó hàm SaveChangesAsync()
được gọi. Tại thời điểm này, việc thực thi quay trở lại MyCallingFunction
và đi vào vòng lặp for viết 'Tiếp tục thực thi' lên đến 1000 lần. Khi SaveChangesAsync()
kết thúc, việc thực thi trở lại SaveRecords
hàm, ghi Save Complete
vào bảng điều khiển. Khi mọi thứ SaveRecords
hoàn tất, quá trình thực thi sẽ tiếp tục MyCallingFunction
đúng như khi SaveChangesAsync()
hoàn thành. Bối rối? Đây là một ví dụ đầu ra:
Chức năng Bắt đầu
Lưu bắt đầu
Tiếp tục thực hiện!
Tiếp tục thực hiện!
Tiếp tục thực hiện!
Tiếp tục thực hiện!
Tiếp tục thực hiện!
....
Tiếp tục thực hiện!
Lưu Hoàn thành!
Tiếp tục thực hiện!
Tiếp tục thực hiện!
Tiếp tục thực hiện!
....
Tiếp tục thực hiện!
Chức năng hoàn thành!
Hoặc có thể:
Chức năng Bắt đầu
Lưu bắt đầu
Tiếp tục thực hiện!
Tiếp tục thực hiện!
Lưu Hoàn thành!
Tiếp tục thực hiện!
Tiếp tục thực hiện!
Tiếp tục thực hiện!
....
Tiếp tục thực hiện!
Chức năng hoàn thành!
Đó là vẻ đẹp của async await
, mã của bạn có thể tiếp tục chạy trong khi bạn đang đợi một thứ gì đó kết thúc. Trong thực tế, bạn sẽ có một chức năng giống như chức năng gọi của bạn:
public async Task MyCallingFunction()
{
List<Task> myTasks = new List<Task>();
myTasks.Add(SaveRecords(GenerateNewRecords()));
myTasks.Add(SaveRecords2(GenerateNewRecords2()));
myTasks.Add(SaveRecords3(GenerateNewRecords3()));
myTasks.Add(SaveRecords4(GenerateNewRecords4()));
await Task.WhenAll(myTasks.ToArray());
}
Ở đây, bạn có bốn chức năng lưu bản ghi khác nhau hoạt động cùng một lúc . MyCallingFunction
sẽ hoàn thành nhanh hơn rất nhiều async await
so với khi các SaveRecords
chức năng riêng lẻ được gọi theo chuỗi.
Một điều mà tôi chưa chạm vào là await
từ khóa. Điều này làm là ngăn chức năng hiện tại thực thi cho đến khi bất cứ điều gì Task
bạn đang chờ hoàn thành. Vì vậy, trong trường hợp của bản gốc MyCallingFunction
, dòng Function Complete
sẽ không được ghi vào bảng điều khiển cho đến khi SaveRecords
chức năng kết thúc.
Tóm lại, nếu bạn có một tùy chọn để sử dụng async await
, bạn nên sử dụng vì nó sẽ làm tăng đáng kể hiệu suất của ứng dụng của bạn.