2015-06-03 13 views
7

Tôi đang cố gắng để chuyển đổi phương pháp sau đây (ví dụ đơn giản) là không đồng bộ, như cuộc gọi cacheMissResolver có thể rất tốn kém về thời gian (tra cứu cơ sở dữ liệu, cuộc gọi mạng):Cách đúng để chuyển đổi phương thức thành không đồng bộ trong C#?

// Synchronous version 
public class ThingCache 
{ 
    private static readonly object _lockObj; 
    // ... other stuff 

    public Thing Get(string key, Func<Thing> cacheMissResolver) 
    { 
     if (cache.Contains(key)) 
      return cache[key]; 

     Thing item; 

     lock(_lockObj) 
     { 
      if (cache.Contains(key)) 
       return cache[key]; 

      item = cacheMissResolver();  
      cache.Add(key, item); 
     } 

     return item; 
    } 
} 

Có rất nhiều vật liệu trên mạng về việc tiêu thụ các phương pháp không đồng bộ, nhưng lời khuyên tôi đã tìm thấy về việc sản xuất chúng dường như ít rõ ràng hơn. Vì đây là một phần của thư viện, một trong những nỗ lực của tôi dưới đây có đúng không?

// Asynchronous attempts 
public class ThingCache 
{ 
    private static readonly SemaphoreSlim _lockObj = new SemaphoreSlim(1); 
    // ... other stuff 

    // attempt #1 
    public async Task<Thing> Get(string key, Func<Thing> cacheMissResolver) 
    { 
     if (cache.Contains(key)) 
      return await Task.FromResult(cache[key]); 

     Thing item; 

     await _lockObj.WaitAsync(); 

     try 
     { 
      if (cache.Contains(key)) 
       return await Task.FromResult(cache[key]); 

      item = await Task.Run(cacheMissResolver).ConfigureAwait(false); 
      _cache.Add(key, item); 
     } 
     finally 
     { 
      _lockObj.Release(); 
     } 

     return item; 
    } 

    // attempt #2 
    public async Task<Thing> Get(string key, Func<Task<Thing>> cacheMissResolver) 
    { 
     if (cache.Contains(key)) 
      return await Task.FromResult(cache[key]); 

     Thing item; 

     await _lockObj.WaitAsync(); 

     try 
     { 
      if (cache.Contains(key)) 
       return await Task.FromResult(cache[key]); 

      item = await cacheMissResolver().ConfigureAwait(false); 
      _cache.Add(key, item); 
     } 
     finally 
     { 
      _lockObj.Release(); 
     } 

     return item; 
    } 
} 

Có sử dụng đúng cách để thay thế tuyên bố khóa bằng phương pháp không đồng bộ không? (Tôi không thể chờ đợi trong nội dung của một tuyên bố khóa.)

Tôi có nên thực hiện đối số cacheMissResolver loại Func<Task<Thing>> thay thế không? Mặc dù điều này đặt gánh nặng của việc đảm bảo func giải quyết là không đồng bộ trên người gọi (gói trong Task.Run, tôi biết nó sẽ được offloaded đến một chủ đề nền nếu phải mất một thời gian dài).

Cảm ơn.

+1

Cân nhắc sử dụng ['AsyncLock'] (https://github.com/StephenCleary/AsyncEx/wiki/AsyncLock). –

+0

'cache' là gì? – dbc

+0

@Timothy Shields - Điều đó có vẻ hữu ích! Cảm ơn. – rob

Trả lời

3

Đang sử dụng SemaphoreLàm cách chính xác để thay thế tuyên bố khóa bằng phương pháp không đồng bộ?

Có.

Tôi có nên đặt đối số cacheMissResolver loại Func<Task<Thing>> thay thế không?

Có. Nó sẽ cho phép người gọi cung cấp một hoạt động không đồng bộ vốn có (chẳng hạn như IO) thay vì thực hiện điều này chỉ phù hợp với công việc đang chạy dài CPU bị ràng buộc hoạt động. (Trong khi vẫn hỗ trợ CPU bị ràng buộc công việc bằng cách đơn giản có người gọi sử dụng Task.Run mình, nếu đó là những gì họ muốn làm.)


Khác hơn thế, chỉ cần lưu ý rằng có không chỉ ở chỗ có await Task.FromResult(...); Việc đóng gói một giá trị trong một Task chỉ để ngay lập tức unwrap nó là vô nghĩa. Chỉ cần sử dụng kết quả trực tiếp trong các tình huống như vậy, trong trường hợp này, trả về giá trị được lưu trữ trực tiếp. Những gì bạn đang làm không thực sự là sai, nó chỉ là không cần thiết phức tạp/khó hiểu mã.

+0

Cảm ơn. Tôi đã không nắm bắt được những tác động của việc sử dụng đang chờ đợi trên Task.FromResult (...). – rob

3

Nếu bộ nhớ cache của bạn ở trong bộ nhớ (có vẻ như vậy), hãy cân nhắc lưu vào bộ nhớ cache các tác vụ thay vì kết quả . Điều này có một thuộc tính bên tốt đẹp nếu hai phương thức yêu cầu cùng một khóa, chỉ một yêu cầu giải quyết duy nhất được thực hiện. Ngoài ra, vì chỉ có bộ nhớ cache bị khóa (và không phải là các hoạt động giải quyết), bạn có thể tiếp tục sử dụng một khóa đơn giản.

public class ThingCache 
{ 
    private static readonly object _lockObj; 

    public async Task<Thing> GetAsync(string key, Func<Task<Thing>> cacheMissResolver) 
    { 
    lock (_lockObj) 
    { 
     if (cache.Contains(key)) 
     return cache[key]; 
     var task = cacheMissResolver(); 
     _cache.Add(key, task); 
    } 
    } 
} 

Tuy nhiên, điều này cũng sẽ lưu vào bộ nhớ ngoại lệ mà bạn không muốn. Một cách để tránh điều này là cho phép nhiệm vụ ngoại lệ để nhập bộ nhớ cache ban đầu, nhưng sau đó prune nó khi yêu cầu tiếp theo được thực hiện:

public class ThingCache 
{ 
    private static readonly object _lockObj; 

    public async Task<Thing> GetAsync(string key, Func<Task<Thing>> cacheMissResolver) 
    { 
    lock (_lockObj) 
    { 
     if (cache.Contains(key)) 
     { 
     if (cache[key].Status == TaskStatus.RanToCompletion) 
      return cache[key]; 
     cache.Remove(key); 
     } 
     var task = cacheMissResolver(); 
     _cache.Add(key, task); 
    } 
    } 
} 

Bạn có thể quyết định kiểm tra thêm này là không cần thiết nếu bạn có một quá trình cắt tỉa bộ nhớ cache định kỳ.

+0

Cảm ơn vì điều đó. Tôi đã không nghĩ đến việc tự lưu trữ bộ nhớ. Chúc mừng cũng đã viết rất nhiều về chủ đề này, đã tìm thấy một số bài viết của bạn hữu ích trong việc tìm hiểu thêm! – rob

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