2012-06-27 25 views
6

Tôi đang làm việc trên một hệ thống mô phỏng, trong số những thứ khác, cho phép thực hiện các tác vụ trong các bước thời gian mô phỏng rời rạc. Việc thực hiện tất cả xảy ra trong ngữ cảnh của chuỗi mô phỏng, nhưng, từ quan điểm của một toán tử '' sử dụng hệ thống, chúng muốn hành xử không đồng bộ. Rất may là TPL, với các từ khóa 'không đồng bộ/chờ đợi' tiện dụng, làm cho điều này khá đơn giản. Tôi có một phương pháp nguyên thủy trên Mô phỏng như sau:Các công việc báo hiệu hiệu quả cho việc hoàn thành TPL trên các sự kiện thường xuyên reoccuring

public Task CycleExecutedEvent() 
    { 
     lock (_cycleExecutedBroker) 
     { 
      if (!IsRunning) throw new TaskCanceledException("Simulation has been stopped"); 
      return _cycleExecutedBroker.RegisterForCompletion(CycleExecutedEventName); 
     } 
    } 

Điều này về cơ bản là tạo một TaskCompletionSource mới và sau đó trả lại một Tác vụ. Mục đích của nhiệm vụ này là thực hiện sự tiếp tục của nó khi 'ExecuteCycle' mới trên mô phỏng xảy ra.

sau đó tôi có một số phương pháp mở rộng như thế này:

public static async Task WaitForDuration(this ISimulation simulation, double duration) 
    { 
     double startTime = simulation.CurrentSimulatedTime; 
     do 
     { 
      await simulation.CycleExecutedEvent(); 
     } while ((simulation.CurrentSimulatedTime - startTime) < duration); 
    } 

    public static async Task WaitForCondition(this ISimulation simulation, Func<bool> condition) 
    { 
     do 
     { 
      await simulation.CycleExecutedEvent(); 
     } while (!condition()); 
    } 

Đây là rất tiện dụng, sau đó, để xây dựng chuỗi từ một góc độ 'điều hành', có những hành động dựa trên điều kiện và chờ đợi khoảng thời gian mô phỏng. Vấn đề tôi đang chạy vào là CycleExecuted xảy ra rất thường xuyên (khoảng mỗi vài phần nghìn giây nếu tôi đang chạy ở tốc độ hoàn toàn tăng tốc). Bởi vì các phương thức trợ giúp 'chờ' này đăng ký một 'await' mới trên mỗi chu kỳ, điều này gây ra doanh thu lớn trong các thể hiện của TaskCompletionSource.

Tôi đã lược tả mã của mình và tôi thấy rằng khoảng 5,5% tổng thời gian CPU của tôi được chi tiêu trong các lần hoàn thành này, trong đó chỉ một phần trăm không đáng kể được chi tiêu trong mã 'hoạt động'. Có hiệu quả tất cả thời gian được dành để đăng ký các lần hoàn thành mới trong khi đợi các điều kiện kích hoạt hợp lệ.

Câu hỏi của tôi: làm cách nào để cải thiện hiệu suất tại đây trong khi vẫn giữ được sự tiện lợi của mẫu không đồng bộ/chờ đợi để viết 'hành vi của nhà điều hành'? Tôi nghĩ rằng tôi cần một cái gì đó như một TaskCompletionSource trọng lượng nhẹ hơn và/hoặc tái sử dụng, cho rằng sự kiện kích hoạt xảy ra thường xuyên.


Tôi đã thực hiện một nghiên cứu thêm chút và có vẻ như là một lựa chọn tốt sẽ tạo ra một thực hiện tùy chỉnh các mô hình Awaitable, mà có thể buộc trực tiếp vào sự kiện này, loại bỏ sự cần thiết cho một loạt các Các trường hợp TaskCompletionSource và Task. Lý do nó có thể hữu ích ở đây là có rất nhiều sự tiếp tục khác nhau đang đợi CycleExecutedEvent và chúng cần phải chờ nó thường xuyên. Vì vậy, lý tưởng tôi đang xem xét một cách để chỉ xếp hàng các cuộc gọi lại tiếp tục, sau đó gọi lại mọi thứ trong hàng đợi bất cứ khi nào sự kiện xảy ra. Tôi sẽ tiếp tục đào bới, nhưng tôi hoan nghênh mọi sự giúp đỡ nếu mọi người biết cách làm sạch.


Đối với bất cứ ai xem câu hỏi này trong tương lai, đây là tùy chỉnh awaiter tôi đặt lại với nhau:

public sealed class CycleExecutedAwaiter : INotifyCompletion 
{ 
    private readonly List<Action> _continuations = new List<Action>(); 

    public bool IsCompleted 
    { 
     get { return false; } 
    } 

    public void GetResult() 
    { 
    } 

    public void OnCompleted(Action continuation) 
    { 
     _continuations.Add(continuation); 
    } 

    public void RunContinuations() 
    { 
     var continuations = _continuations.ToArray(); 
     _continuations.Clear(); 
     foreach (var continuation in continuations) 
      continuation(); 
    } 

    public CycleExecutedAwaiter GetAwaiter() 
    { 
     return this; 
    } 
} 

Và trong Simulator:

private readonly CycleExecutedAwaiter _cycleExecutedAwaiter = new CycleExecutedAwaiter(); 

    public CycleExecutedAwaiter CycleExecutedEvent() 
    { 
     if (!IsRunning) throw new TaskCanceledException("Simulation has been stopped"); 
     return _cycleExecutedAwaiter; 
    } 

Đó là một chút buồn cười, như người chờ đợi không bao giờ báo cáo Hoàn thành, nhưng hỏa hoạn tiếp tục gọi số lần hoàn thành khi chúng được đăng ký; nó vẫn hoạt động tốt cho ứng dụng này. Điều này làm giảm chi phí CPU từ 5,5% xuống 2,1%. Nó có thể sẽ vẫn yêu cầu một số tinh chỉnh, nhưng đó là một cải tiến tốt đẹp so với bản gốc.

+1

Xin chào, không công bằng khi trả lời câu hỏi của riêng bạn một phút trước tôi! :-) – svick

+0

@svick, chưa được trả lời đầy đủ; Tôi vẫn cần phải tìm ra cách tạo ra tùy chỉnh chờ đợi. :) Cảm ơn các liên kết; chúng khá hữu ích. –

+0

@DanBryant: bạn nên trả lời câu hỏi của bạn ... như là một câu trả lời chứ không phải là trong cơ thể của câu hỏi của riêng bạn. – user7116

Trả lời

5

Từ khóa await không hoạt động chỉ trên Task s, nó hoạt động trên mọi thứ theo mẫu awaitable. Để biết chi tiết, xem Stephen Toub's article await anything;.

Phiên bản ngắn gọn là loại phải có một phương pháp GetAwaiter() trả về một kiểu mà thực hiện INotifyCompletion và cũng có IsCompleted tài sản và phương pháp GetResult() (void -returning, nếu biểu thức await không nên có một giá trị). Ví dụ: xem TaskAwaiter.

Nếu bạn tạo của riêng bạn awaitable, bạn có thể trả lại cùng một đối tượng mỗi lần, tránh chi phí phân bổ nhiều TaskCompletionSource s.

+0

Tôi xây dựng một tùy chỉnh awaitable như bạn đề nghị; nó nhanh hơn nhiều, mặc dù vẫn còn chỗ để cải thiện, tôi chắc chắn. Điều chính khiến tôi lo ngại về nó là nó khá 'rò rỉ' (các lớp bên ngoài có thể lấy bản sao Awaiter và gọi RunContinuations để kích hoạt nó bên ngoài bối cảnh dự kiến.) –

+0

@DanBryant Tôi không chắc bạn có thể làm bất cứ điều gì về điều đó. Nếu bạn cung cấp cho ai đó một cách để lên lịch các lần tiếp tục (đó là những gì bạn muốn), họ luôn có thể sử dụng nó ngoài ngữ cảnh dự kiến. – svick

0

Bạn có thực sự cần nhận được WaitForDurationđối với một chủ đề khác không? Nếu không, bạn chỉ có thể đăng ký gọi lại (hoặc một sự kiện) với _cycleExecutedBroker và nhận thông báo đồng bộ.Trong cuộc gọi lại, bạn có thể kiểm tra bất kỳ điều kiện nào bạn thích và chỉ khi điều kiện đó hóa ra là đúng, hãy thông báo cho một luồng khác (sử dụng tác vụ hoặc tin nhắn hoặc bất kỳ cơ chế nào). Tôi hiểu điều kiện bạn kiểm tra hiếm khi đánh giá là đúng, vì vậy bạn tránh hầu hết các cuộc gọi qua luồng theo cách đó.

Tôi đoán ý chính của câu trả lời của tôi là: Cố gắng giảm số lượng tin nhắn qua luồng bằng cách di chuyển tính toán đến chuỗi "nguồn".

+0

Nó thực sự không vượt qua ranh giới của luồng; vì TaskCompletionSource được đánh dấu là đã hoàn thành trên chuỗi mô phỏng, tất cả các lần hoàn thành thực hiện trên luồng mô phỏng. Toàn bộ điểm sử dụng async/await trong trường hợp này là để giữ cho tất cả các tác vụ đang chạy trên cùng một luồng, với kiểm soát 'năng suất' để chỉ kiểm tra một lần cho mỗi chu kỳ thực hiện mô phỏng. –

+0

Ok, công việc vẫn có chi phí mà các sự kiện đơn giản không. Trong suốt vòng đời của họ, họ yêu cầu nhiều hoạt động liên khóa là khóa phần cứng và rất tốn kém. Chúng là một tài nguyên toàn cầu của hệ thống. – usr

1

Đây là phiên bản của tôi về ReusableAwaiter mô phỏng TaskCompletionSource

public sealed class ReusableAwaiter<T> : INotifyCompletion 
{ 
    private Action _continuation = null; 
    private T _result = default(T); 
    private Exception _exception = null; 

    public bool IsCompleted 
    { 
     get; 
     private set; 
    } 

    public T GetResult() 
    { 
     if (_exception != null) 
      throw _exception; 
     return _result; 
    } 

    public void OnCompleted(Action continuation) 
    { 
     if (_continuation != null) 
      throw new InvalidOperationException("This ReusableAwaiter instance has already been listened"); 
     _continuation = continuation; 
    } 

    /// <summary> 
    /// Attempts to transition the completion state. 
    /// </summary> 
    /// <param name="result"></param> 
    /// <returns></returns> 
    public bool TrySetResult(T result) 
    { 
     if (!this.IsCompleted) 
     { 
      this.IsCompleted = true; 
      this._result = result; 

      if (_continuation != null) 
       _continuation(); 
      return true; 
     } 
     return false; 
    } 

    /// <summary> 
    /// Attempts to transition the exception state. 
    /// </summary> 
    /// <param name="result"></param> 
    /// <returns></returns> 
    public bool TrySetException(Exception exception) 
    { 
     if (!this.IsCompleted) 
     { 
      this.IsCompleted = true; 
      this._exception = exception; 

      if (_continuation != null) 
       _continuation(); 
      return true; 
     } 
     return false; 
    } 

    /// <summary> 
    /// Reset the awaiter to initial status 
    /// </summary> 
    /// <returns></returns> 
    public ReusableAwaiter<T> Reset() 
    { 
     this._result = default(T); 
     this._continuation = null; 
     this._exception = null; 
     this.IsCompleted = false; 
     return this; 
    } 

    public ReusableAwaiter<T> GetAwaiter() 
    { 
     return this; 
    } 
} 

Và đây là các mã kiểm tra.

class Program 
{ 
    static readonly ReusableAwaiter<int> _awaiter = new ReusableAwaiter<int>(); 

    static void Main(string[] args) 
    { 
     Task.Run(() => Test()); 

     Console.ReadLine(); 
     _awaiter.TrySetResult(22); 
     Console.ReadLine(); 
     _awaiter.TrySetException(new Exception("ERR")); 

     Console.ReadLine(); 
    } 

    static async void Test() 
    { 

     int a = await AsyncMethod(); 
     Console.WriteLine(a); 
     try 
     { 
      await AsyncMethod(); 
     } 
     catch(Exception ex) 
     { 
      Console.WriteLine(ex.Message); 
     } 

    } 

    static ReusableAwaiter<int> AsyncMethod() 
    { 
     return _awaiter.Reset(); 
    } 

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