2010-08-24 38 views
5

Tôi đang sử dụng mẫu người đăng ký/trình thông báo để tăng và tiêu thụ các sự kiện từ tầng trung bình .Net của tôi trong C#. Ví dụ, một số sự kiện được nêu trong "bursts" khi dữ liệu được lưu giữ từ một chương trình batch đang nhập một tệp. Điều này thực hiện một nhiệm vụ có khả năng chạy dài và tôi muốn tránh việc kích hoạt sự kiện vài lần một giây bằng cách triển khai "khoảng thời gian yên tĩnh", nhờ đó hệ thống sự kiện đợi cho đến khi luồng sự kiện chậm lại để xử lý sự kiện.Tôi nên triển khai "khoảng thời gian yên tĩnh" như thế nào khi tăng sự kiện?

Tôi nên làm như thế nào khi Nhà xuất bản đóng vai trò tích cực trong việc thông báo cho người đăng ký? Tôi không muốn đợi cho đến khi một sự kiện đến để kiểm tra xem có người nào khác đang chờ đợi trong khoảng thời gian yên tĩnh không ...

Không có quy trình lưu trữ nào để thăm dò mô hình đăng ký tại thời điểm này. Tôi có nên từ bỏ mẫu xuất bản/đăng ký hoặc có cách nào tốt hơn không?

Trả lời

1

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.

+0

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ó. –

+0

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. –

1

Tôi không chắc chắn nếu tôi hiểu câu hỏi của bạn một cách chính xác, nhưng tôi sẽ cố gắng khắc phục sự cố tại nguồn - đảm bảo các sự kiện không được nêu trong "cụm". Bạn có thể xem xét việc thực hiện các hoạt động hàng loạt, có thể được sử dụng từ chương trình nhập tệp. Điều này sẽ được coi là một sự kiện duy nhất trong middletier của bạn và tăng một sự kiện duy nhất.

Tôi nghĩ sẽ rất khó để thực hiện một số giải pháp hợp lý nếu bạn không thể thực hiện thay đổi được nêu ở trên - bạn có thể cố gắng đưa nhà xuất bản của mình vào một nhà xuất bản "lưu vào bộ nhớ cache". nếu họ đang đến trong vụ nổ. Cách dễ nhất là nhớ cache một sự kiện nếu một biến thể khác cùng loại đang được xử lý (do đó, lô của bạn sẽ gây ra ít nhất 2 sự kiện - một sự kiện ngay từ đầu và một ở cuối). Bạn có thể chờ một thời gian ngắn và chỉ tăng một sự kiện khi người tiếp theo không đến trong thời gian đó, nhưng bạn có thời gian trễ ngay cả khi có một sự kiện duy nhất trong kênh. Bạn cũng cần phải chắc chắn rằng bạn sẽ nâng cao sự kiện theo thời gian ngay cả khi có hàng đợi sự kiện liên tục - nếu không nhà xuất bản sẽ có khả năng bị bỏ đói.

Lựa chọn thứ hai là khó khăn để thực hiện và sẽ chứa chẩn đoán, mà có thể đi rất sai ...

+0

Tôi muốn quy trình thông báo có thể tăng sự kiện thường xuyên, nhưng yêu cầu người đăng ký quyết định có quan tâm đến các sự kiện hay không. Một người đăng ký có thể muốn hai mươi sự kiện mỗi giây, người khác có thể muốn được thông báo ít thường xuyên hơn ... –

+0

Ah ok, tôi hiểu ngay bây giờ. Nếu bạn muốn sử dụng mẫu xuất bản/đăng ký thì tôi nghĩ rằng người đăng ký sẽ cần triển khai bộ nhớ đệm của các sự kiện. Bạn có thể xem xét việc tạo các loại sự kiện khác nhau, ví dụ: một trong đó sẽ cháy cho mọi yếu tố duy nhất, cái kia sẽ cháy một lần cho toàn bộ lô. Nếu không, tôi nghĩ tôi sẽ triển khai bộ nhớ đệm của các sự kiện bên trong mỗi người đăng ký. – Grzenio

+0

Có thể cho rằng, 'tần suất' có thể là tài sản của đăng ký, buộc nhà xuất bản phải gửi thông báo theo các khoảng thời gian khác nhau cho những người đăng ký khác nhau. –

0

Đây là một ý tưởng chỉ rơi khỏi đầu tôi. Tôi không biết làm thế nào khả thi nó là và không thể nhìn thấy một cách rõ ràng để làm cho nó chung chung hơn, nhưng nó có thể là một sự khởi đầu. Tất cả nó là cung cấp một bộ đệm cho các sự kiện bấm nút (thay thế với sự kiện của bạn khi cần thiết).

class ButtonClickBuffer 
{ 
    public event EventHandler BufferedClick; 

    public ButtonClickBuffer(Button button, int queueSize) 
    { 
     this.queueSize= queueSize; 
     button.Click += this.button_Click; 
    } 

    private int queueSize; 
    private List<EventArgs> queuedEvents = new List<EventArgs>(); 

    private void button_Click(object sender, EventArgs e) 
    { 
     queuedEvents.Add(e); 
     if (queuedEvents.Count >= queueSize) 
     { 
      if (this.BufferedClick!= null) 
      { 
       foreach (var args in this.queuedEvents) 
       { 
        this.BufferedClick(sender, args); 
       } 
       queuedEvents.Clear(); 
      } 
     } 
    } 
} 

Vì vậy thuê bao của bạn, thay vì đăng ký như:

this.button1.Click += this.button1_Click; 

có sử dụng một bộ đệm, xác định có bao nhiêu sự kiện chờ:

ButtonClickBuffer buffer = new ButtonClickBuffer(this.button1, 5); 
buffer.BufferedClick += this.button1_Click; 

Nó hoạt động trong một hình thức kiểm tra đơn giản Tôi gõ cửa, nhưng nó đã sẵn sàng cho sản xuất!

Bạn đã nói rằng bạn không muốn đợi một sự kiện để xem liệu có đợi hàng đợi hay không, đó chính xác là điều gì xảy ra. Bạn có thể thay thế logic bên trong bộ đệm để sinh ra một luồng mới để theo dõi hàng đợi và gửi các sự kiện khi cần thiết. Thiên Chúa biết những gì các vấn đề luồng và khóa có thể phát sinh từ đó!

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