Dưới đây là một việc thực hiện thô mà có thể chỉ cho bạn theo một hướng. Trong ví dụ của tôi, nhiệm vụ liên quan đến thông báo là lưu một đối tượng dữ liệu. Khi một đối tượng được lưu, sự kiện đã lưu được nâng lên. Ngoài phương thức Save đơn giản, tôi đã thực hiện các phương thức BeginSave và EndSave cũng như quá tải của Save để làm việc với hai phương thức đó để lưu trữ hàng loạt. Khi EndSave được gọi, một sự kiện BatchSaved duy nhất được kích hoạt.
Rõ ràng, bạn có thể thay đổi điều này cho phù hợp với nhu cầu của mình. Trong ví dụ của tôi, tôi theo dõi danh sách tất cả các đối tượng đã được lưu trong quá trình thực hiện hàng loạt, nhưng điều này có thể không phải là thứ bạn cần làm ... bạn chỉ có thể quan tâm đến số lượng đối tượng đã được lưu hoặc thậm chí đơn giản rằng hoạt động lưu hàng loạt đã được hoàn thành. Nếu bạn dự đoán một số lượng lớn các đối tượng đang được lưu, sau đó lưu trữ chúng trong một danh sách như trong ví dụ của tôi có thể trở thành một vấn đề bộ nhớ.
EDIT: Tôi đã thêm một khái niệm "ngưỡng" vào ví dụ của mình nhằm cố gắng ngăn chặn một số lượng lớn các đối tượng được giữ trong bộ nhớ. Điều này gây ra sự kiện BatchSaved để bắn thường xuyên hơn, mặc dù. Tôi cũng thêm một số khóa để giải quyết an toàn chủ đề tiềm năng, mặc dù tôi có thể đã bỏ lỡ một cái gì đó ở đó.
class DataConcierge<T>
{
// *************************
// Simple save functionality
// *************************
public void Save(T dataObject)
{
// perform save logic
this.OnSaved(dataObject);
}
public event DataObjectSaved<T> Saved;
protected void OnSaved(T dataObject)
{
var saved = this.Saved;
if (saved != null)
saved(this, new DataObjectEventArgs<T>(dataObject));
}
// ************************
// Batch save functionality
// ************************
Dictionary<BatchToken, List<T>> _BatchSavedDataObjects = new Dictionary<BatchToken, List<T>>();
System.Threading.ReaderWriterLockSlim _BatchSavedDataObjectsLock = new System.Threading.ReaderWriterLockSlim();
int _SavedObjectThreshold = 17; // if the number of objects being stored for a batch reaches this threshold, then those objects are to be cleared from the list.
public BatchToken BeginSave()
{
// create a batch token to represent this batch
BatchToken token = new BatchToken();
_BatchSavedDataObjectsLock.EnterWriteLock();
try
{
_BatchSavedDataObjects.Add(token, new List<T>());
}
finally
{
_BatchSavedDataObjectsLock.ExitWriteLock();
}
return token;
}
public void EndSave(BatchToken token)
{
List<T> batchSavedDataObjects;
_BatchSavedDataObjectsLock.EnterWriteLock();
try
{
if (!_BatchSavedDataObjects.TryGetValue(token, out batchSavedDataObjects))
throw new ArgumentException("The BatchToken is expired or invalid.", "token");
this.OnBatchSaved(batchSavedDataObjects); // this causes a single BatchSaved event to be fired
if (!_BatchSavedDataObjects.Remove(token))
throw new ArgumentException("The BatchToken is expired or invalid.", "token");
}
finally
{
_BatchSavedDataObjectsLock.ExitWriteLock();
}
}
public void Save(BatchToken token, T dataObject)
{
List<T> batchSavedDataObjects;
// the read lock prevents EndSave from executing before this Save method has a chance to finish executing
_BatchSavedDataObjectsLock.EnterReadLock();
try
{
if (!_BatchSavedDataObjects.TryGetValue(token, out batchSavedDataObjects))
throw new ArgumentException("The BatchToken is expired or invalid.", "token");
// perform save logic
this.OnBatchSaved(batchSavedDataObjects, dataObject);
}
finally
{
_BatchSavedDataObjectsLock.ExitReadLock();
}
}
public event BatchDataObjectSaved<T> BatchSaved;
protected void OnBatchSaved(List<T> batchSavedDataObjects)
{
lock (batchSavedDataObjects)
{
var batchSaved = this.BatchSaved;
if (batchSaved != null)
batchSaved(this, new BatchDataObjectEventArgs<T>(batchSavedDataObjects));
}
}
protected void OnBatchSaved(List<T> batchSavedDataObjects, T savedDataObject)
{
// add the data object to the list storing the data objects that have been saved for this batch
lock (batchSavedDataObjects)
{
batchSavedDataObjects.Add(savedDataObject);
// if the threshold has been reached
if (_SavedObjectThreshold > 0 && batchSavedDataObjects.Count >= _SavedObjectThreshold)
{
// then raise the BatchSaved event with the data objects that we currently have
var batchSaved = this.BatchSaved;
if (batchSaved != null)
batchSaved(this, new BatchDataObjectEventArgs<T>(batchSavedDataObjects.ToArray()));
// and clear the list to ensure that we are not holding on to the data objects unnecessarily
batchSavedDataObjects.Clear();
}
}
}
}
class BatchToken
{
static int _LastId = 0;
static object _IdLock = new object();
static int GetNextId()
{
lock (_IdLock)
{
return ++_LastId;
}
}
public BatchToken()
{
this.Id = GetNextId();
}
public int Id { get; private set; }
}
class DataObjectEventArgs<T> : EventArgs
{
public T DataObject { get; private set; }
public DataObjectEventArgs(T dataObject)
{
this.DataObject = dataObject;
}
}
delegate void DataObjectSaved<T>(object sender, DataObjectEventArgs<T> e);
class BatchDataObjectEventArgs<T> : EventArgs
{
public IEnumerable<T> DataObjects { get; private set; }
public BatchDataObjectEventArgs(IEnumerable<T> dataObjects)
{
this.DataObjects = dataObjects;
}
}
delegate void BatchDataObjectSaved<T>(object sender, BatchDataObjectEventArgs<T> e);
Trong ví dụ của mình, tôi chọn sử dụng khái niệm mã thông báo để tạo các lô riêng biệt. Điều này cho phép các hoạt động hàng loạt nhỏ hơn chạy trên các luồng riêng biệt để hoàn thành và nâng cao sự kiện mà không cần đợi thao tác xử lý hàng loạt lớn hơn để hoàn thành.
Tôi đã thực hiện các sự kiện tách biệt: Đã lưu và Đã lưu hàng loạt. Tuy nhiên, chúng có thể dễ dàng được hợp nhất thành một sự kiện duy nhất.
CHỈNH SỬA: điều kiện cuộc đua cố định được chỉ ra bởi Steven Sudit khi truy cập các đại biểu sự kiện.
EDIT: sửa đổi mã khóa trong ví dụ của tôi để sử dụng ReaderWriterLockSlim thay vì Monitor (tức là "khóa" tuyên bố). Tôi nghĩ rằng có một vài điều kiện chủng tộc, chẳng hạn như giữa các phương pháp Save và EndSave. Có thể cho EndSave thực thi, khiến danh sách các đối tượng dữ liệu được xóa khỏi từ điển. Nếu phương thức Save được thực hiện cùng một lúc trên một luồng khác, nó sẽ có thể cho một đối tượng dữ liệu được thêm vào danh sách đó, mặc dù nó đã được loại bỏ khỏi từ điển.
Trong ví dụ đã sửa đổi của tôi, tình huống này không thể xảy ra và phương thức Lưu sẽ ném một ngoại lệ nếu nó thực thi sau khi EndSave. Những điều kiện chủng tộc này đã được gây ra chủ yếu bởi tôi cố gắng tránh những gì tôi nghĩ là khóa không cần thiết. Tôi nhận ra rằng nhiều mã cần thiết để được trong vòng một khóa, nhưng quyết định sử dụng ReaderWriterLockSlim thay vì Monitor vì tôi chỉ muốn ngăn chặn Save và EndSave thực thi cùng một lúc; không cần phải ngăn nhiều luồng từ thực thi Lưu cùng một lúc. Lưu ý rằng Màn hình vẫn được sử dụng để đồng bộ hóa quyền truy cập vào danh sách các đối tượng dữ liệu cụ thể được truy xuất từ từ điển.
CHỈNH SỬA: ví dụ sử dụng được thêm
Dưới đây là ví dụ về cách sử dụng cho mã mẫu ở trên.
static void DataConcierge_Saved(object sender, DataObjectEventArgs<Program.Customer> e)
{
Console.WriteLine("DataConcierge<Customer>.Saved");
}
static void DataConcierge_BatchSaved(object sender, BatchDataObjectEventArgs<Program.Customer> e)
{
Console.WriteLine("DataConcierge<Customer>.BatchSaved: {0}", e.DataObjects.Count());
}
static void Main(string[] args)
{
DataConcierge<Customer> dc = new DataConcierge<Customer>();
dc.Saved += new DataObjectSaved<Customer>(DataConcierge_Saved);
dc.BatchSaved += new BatchDataObjectSaved<Customer>(DataConcierge_BatchSaved);
var token = dc.BeginSave();
try
{
for (int i = 0; i < 100; i++)
{
var c = new Customer();
// ...
dc.Save(token, c);
}
}
finally
{
dc.EndSave(token);
}
}
Điều này dẫn đến kết quả như sau:
DataConcierge < khách hàng> .BatchSaved: 17
DataConcierge < khách hàng> .BatchSaved: 17
DataConcierge < khách hàng> .BatchSaved : 17
Đạt aConcierge < khách hàng> .BatchSaved: 17
DataConcierge < khách hàng> .BatchSaved: 17
DataConcierge < khách hàng> .BatchSaved: 15
Ngưỡng trong ví dụ của tôi được thiết lập đến 17, do đó, một lô 100 mục gây ra sự kiện BatchSaved để bắn 6 lần.
Khiếu nại tiêu chuẩn: Không kiểm tra đại biểu cho null và sau đó gọi nó, như là một điều kiện đua. Thay vào đó, tạo một bản sao cục bộ, kiểm tra nó cho null và sau đó gọi qua nó. –
Ah, vâng. Cảm ơn bạn đã chỉ ra điều đó. Tôi đã cập nhật theo đề xuất của bạn. –