10

Tôi đang sử dụng ASP.NET Core và EF Core có SaveChangesSaveChangesAsync.Làm cách nào để gọi một cách an toàn phương thức không đồng bộ từ các SaveChanges không đồng bộ của EF?

Trước khi lưu vào cơ sở dữ liệu, trong DbContext của tôi, tôi thực hiện một số kiểm toán/logging:

public async Task LogAndAuditAsync() { 
    // do async stuff 
} 

public override int SaveChanges { 
    /*await*/ LogAndAuditAsync();  // what do I do here??? 
    return base.SaveChanges(); 
} 

public override async Task<int> SaveChangesAsync { 
    await LogAndAuditAsync(); 
    return await base.SaveChanges(); 
} 

Vấn đề là đồng bộ SaveChanges().

Tôi luôn làm "không đồng bộ tất cả các con đường xuống", nhưng ở đây điều đó là không thể. Tôi có thể thiết kế lại để có LogAndAudit()LogAndAuditAsync() nhưng đó không phải là DRY và tôi sẽ cần phải thay đổi một tá phần lớn mã khác không thuộc về tôi.

Có rất nhiều câu hỏi khác về chủ đề này và tất cả đều chung và phức tạp và đầy tranh luận. Tôi cần biết cách tiếp cận an toàn nhất trong trường hợp cụ thể này.

Vì vậy, trong SaveChanges(), làm cách nào để gọi một cách an toàn và đồng bộ một phương thức không đồng bộ, mà không có deadlock?

+0

Bạn đã thử sử dụng công việc chờ đợi Task.Run (() => {...}); –

+0

@ H.Herzl Đúng vậy. Nhưng nó không phải là tôi không thể làm cho nó để biên dịch, hoặc để chạy. Đó là điều tôi cần biết, trong trường hợp cụ thể này, cách an toàn nhất * là gì, giúp tránh được khả năng gây chết người. Thật dễ dàng để có được đồng bộ hóa trên async rất sai. – grokky

+0

Sidestepping vấn đề, là nó có thể sử dụng một hàng đợi cho việc đăng nhập/kiểm toán và sau đó hoạt động đăng nhập/kiểm toán trở thành async của thiên nhiên, vì vậy bạn không cần async/await có ở tất cả? – Menahem

Trả lời

0

Có nhiều cách để thực hiện đồng bộ hóa không đồng bộ hóa và mỗi cách có gotchas. Nhưng tôi cần phải biết an toàn nhất là cho trường hợp sử dụng cụ thể này.

Câu trả lời là sử dụng Stephen Cleary của "Thread Pool Hack":

Task.Run(() => LogAndAuditAsync()).GetAwaiter().GetResult(); 

Lý do là trong phương pháp, công việc cơ sở dữ liệu chỉ hơn được thực hiện, không có gì khác. Bạn không cần bối cảnh đồng bộ hóa ban đầu - trong số DbContext của EF Core, bạn không cần truy cập vào HttpContext của ASP.NET Core!

Do đó, cách tốt nhất là giảm tải hoạt động vào nhóm luồng và tránh các khóa chết.

9

Cách đơn giản nhất để gọi một phương pháp async từ một phương pháp phi async là sử dụng GetAwaiter().GetResult():

public override int SaveChanges { 
    LogAndAuditAsync().GetAwaiter().GetResult(); 
    return base.SaveChanges(); 
} 

này sẽ đảm bảo rằng một ngoại lệ ném vào LogAndAuditAsync không xuất hiện như một AggregateException trong SaveChanges. Thay vào đó, ngoại lệ ban đầu được phổ biến.

Tuy nhiên, nếu mã đang thực thi trên ngữ cảnh đồng bộ hóa đặc biệt có thể bế tắc khi thực hiện đồng bộ hóa quá mức (ví dụ: ASP.NET, Winforms và WPF) thì bạn phải cẩn thận hơn.

Mỗi lần mã trong LogAndAuditAsync sử dụng await, nó sẽ đợi công việc hoàn thành. Nếu tác vụ này phải thực thi trên ngữ cảnh đồng bộ hóa hiện đang bị chặn bởi lệnh gọi đến số LogAndAuditAsync().GetAwaiter().GetResult() bạn có bế tắc.

Để tránh điều này, bạn cần thêm .ConfigureAwait(false) vào tất cả các cuộc gọi await trong số LogAndAuditAsync. Ví dụ.

await file.WriteLineAsync(...).ConfigureAwait(false); 

Lưu ý rằng sau await mã này sẽ tiếp tục thực hiện bên ngoài ngữ cảnh đồng bộ hóa (trên công cụ lên lịch công việc).

Nếu điều đó là không thể lựa chọn cuối cùng của bạn là để bắt đầu một nhiệm vụ mới vào các nhiệm vụ hồ bơi scheduler:

Task.Run(() => LogAndAuditAsync()).GetAwaiter().GetResult(); 

này vẫn sẽ chặn bối cảnh đồng bộ nhưng LogAndAuditAsync sẽ thực hiện trên lịch trình nhiệm vụ hồ bơi và không bế tắc vì nó không phải nhập bối cảnh đồng bộ hóa bị chặn.

+0

Tôi biết cú pháp này, nhưng tại sao nó lại là phương pháp an toàn nhất trong trường hợp sử dụng này, cho sự kiên trì trong EF Core và ASP.NET Core? Có một số cách để thực hiện đồng bộ hóa quá mức không đồng bộ, đây là một trong số chúng. – grokky

Các vấn đề liên quan