Giống như Gman đã đề cập ConcurrentDictionary
là cách ưu tiên để thực hiện việc này, tuy nhiên nếu điều đó không có sẵn cho câu lệnh đơn giản lock
thì sẽ đủ.
static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
var d = new Dictionary<A, R>();
return a=>
{
R r;
lock(d)
{
if (!d.TryGetValue(a, out r))
{
r = f(a);
d.Add(a, r);
}
}
return r;
};
}
Một vấn đề có thể xảy ra khi sử dụng khóa thay vì ConcurrentDictionary
là phương pháp này có thể giới thiệu khóa chết vào chương trình của bạn.
- Bạn có hai chức năng memoized
_memo1 = Func1.Memoize()
và _memo2 = Func2.Memoize()
, nơi _memo1
và _memo2
là các biến ví dụ.
- Thread1 gọi
_memo1
, Func1
bắt đầu xử lý.
- Thread2 gọi
_memo2
, bên trong Func2
có cuộc gọi đến số _memo1
và chuỗi Thread2.
- Quá trình xử lý
Func1
của Thread1 được gọi đến _memo2
vào cuối hàm, khối Thread1.
- DEADLOCK!
Vì vậy, nếu có thể, hãy sử dụng ConcurrentDictionary
, nhưng nếu bạn không thể và bạn sử dụng khóa thay vì không gọi các chức năng Ghi nhớ khác nằm ngoài chức năng bạn đang chạy khi ở trong chức năng Ghi nhớ hoặc bạn mở bản thân bạn có nguy cơ bị deadlocks (nếu _memo1
và _memo2
là các biến cục bộ thay vì các biến mẫu, thì bế tắc sẽ không xảy ra).
(Lưu ý, hiệu suất có thể được cải thiện đôi chút bằng cách sử dụng ReaderWriterLock
nhưng bạn vẫn sẽ có cùng một vấn đề bế tắc.)
Nguồn
2013-12-12 15:58:01
Lưu ý rằng 'GetOrAdd' sẽ không ngăn chặn hoàn toàn f được gọi nhiều lần cho một đối số nhất định; nó chỉ đảm bảo rằng kết quả của chỉ * một * của các lời gọi được thêm vào từ điển. Bạn có thể nhận được nhiều hơn một lời gọi trong trường hợp chủ đề kiểm tra bộ nhớ cache đồng thời trước khi giá trị được lưu trữ đã được thêm vào. Nó thường không đáng lo ngại về điều này, nhưng tôi đề cập đến nó trong trường hợp yêu cầu có tác dụng phụ không mong muốn. –
@ JamesWorld Yep, đúng vậy. Câu trả lời đã chỉnh sửa để phản ánh điều đó, cảm ơn bạn! – Gman
Tôi hơi bối rối - không phải 'cache' ở đây là biến cục bộ? Mỗi lần 'ThreadsafeMemoize()' được gọi, nó sẽ không tạo một từ điển mới? – dashnick