2010-04-02 28 views
14

Tôi đã có một tình huống xuất hiện yêu cầu chạy biểu thức lambda trên chuỗi giao diện người dùng sau một thời gian trễ. Tôi đã nghĩ ra một số cách để làm điều này và cuối cùng đã giải quyết theo cách tiếp cận này.NET: Cách tốt nhất để thực thi lambda trên chuỗi giao diện người dùng sau khi trì hoãn?

Task.Factory.StartNew(() => Thread.Sleep(1000)) 
    .ContinueWith((t) => textBlock.Text="Done",TaskScheduler.FromCurrentSynchronizationContext()); 

Nhưng tôi tự hỏi liệu có cách nào dễ dàng hơn mà tôi bỏ sót hay không. Bất kỳ đề xuất cho một kỹ thuật ngắn hơn, đơn giản hơn hoặc dễ dàng hơn? Giả sử .NET 4 có sẵn.

+1

Nếu bạn đang sử dụng WPF sử dụng [DispatcherTimer] (http: // MSDN .microsoft.com/vi-us/library/system.windows.threading.dispatchertimer.aspx) –

Trả lời

22

Tôi nghĩ những gì bạn có là Scott khá tốt.

Vấn đề nhỏ duy nhất tôi nghĩ một số có thể có với nó, là bạn đang chặn một chuỗi để thực hiện sự chậm trễ của bạn. Tất nhiên đó là một chủ đề nền, và không có khả năng gây ra vấn đề, trừ khi bạn thực hiện rất nhiều các cuộc gọi đồng thời (mỗi buộc lên một chủ đề), nhưng nó vẫn có thể suboptimal.

Thay vào đó, tôi khuyên bạn nên tính toán thuật toán thành một phương thức tiện ích và tránh sử dụng Thread.Sleep.

Có rõ ràng là lẽ vô số cách để làm điều này, nhưng đây là một:

public static class UICallbackTimer 
{ 
    public static void DelayExecution(TimeSpan delay, Action action) 
    { 
     System.Threading.Timer timer = null; 
     SynchronizationContext context = SynchronizationContext.Current; 

     timer = new System.Threading.Timer(
      (ignore) => 
      { 
       timer.Dispose(); 

       context.Post(ignore2 => action(), null); 
      }, null, delay, TimeSpan.FromMilliseconds(-1)); 
    } 
} 

Cách sử dụng:

UICallbackTimer.DelayExecution(TimeSpan.FromSeconds(1), 
     () => textBlock.Text="Done"); 

Tất nhiên bạn cũng có thể viết một thực hiện của phương pháp DelayExecution này mà sử dụng các loại khác của bộ đếm thời gian như DispatcherTimer WPF hoặc lớp WinForms Timer. Tôi không chắc chắn những gì các sự cân bằng của những giờ khác nhau sẽ được. Đoán của tôi sẽ là DispatcherTimer và bộ đếm thời gian của WinForm sẽ thực sự vẫn hoạt động trên các ứng dụng của kiểu đối diện.

EDIT:

Đọc lại câu trả lời của tôi, tôi nghĩ rằng thực sự tôi sẽ bị cám dỗ để yếu tố này vào một phương pháp mở rộng hoạt động trên bối cảnh đồng bộ - nếu bạn nghĩ về nó, một tuyên bố tổng quát hơn sẽ mà bạn cần để có thể đăng công việc trở lại ngữ cảnh đồng bộ hóa sau một thời gian trễ nhất định.

SynchronizationContext đã có phương thức đăng để xếp hàng công việc mà người gọi ban đầu không muốn chặn khi hoàn thành.Điều chúng ta cần là một phiên bản thực tế này, đăng tải công việc sau một thời gian, vì vậy thay vì:

public static class SyncContextExtensions 
{ 
    public static void Post(this SynchronizationContext context, TimeSpan delay, Action action) 
    { 
     System.Threading.Timer timer = null; 

     timer = new System.Threading.Timer(
      (ignore) => 
      { 
       timer.Dispose(); 

       context.Post(ignore2 => action(), null); 
      }, null, delay, TimeSpan.FromMilliseconds(-1)); 
    } 
} 

và sử dụng:

 SynchronizationContext.Current.Post(TimeSpan.FromSeconds(1), 
      () => textBlock.Text="Done"); 
+0

Tôi thích cách tiếp cận phương pháp mở rộng của bạn! Đó là cú pháp rõ ràng và tránh được lệnh gọi sleep(). Sự phá hủy duy nhất là cần thêm một lớp mở rộng tĩnh vào dự án, nhưng đó không phải là một vấn đề lớn. Cảm ơn! –

+0

Hoạt động không cần thiết! – AVEbrahimi

+0

Có thể hủy bỏ hành động trước khi thực hiện hành động bị trì hoãn không? – Kiquenet

2

Tôi nghĩ cách đơn giản nhất là sử dụng System.Windows.Forms.Timer, nếu lambda không phải là một số chức năng ngẫu nhiên.

this._timer.Interval = 1000; 
this._timer.Tick += (s, e) => this.textBlock.Text = "Done"; 

Nếu labda không cần thực hiện trong vòng lặp, hãy thêm điều này;

this.timer1.Tick += (s, e) => this.timer1.Stop(); 

Và gọi

this.timer1.Start(); 

nơi nó cần thiết.

Một cách khác là sử dụng phương thức Gọi.

delegate void FooHandler(); 

private void button1_Click(object sender, EventArgs e) 
     { 

      FooHandler handle =() => Thread.Sleep(1000); 
      handle.BeginInvoke(result => { ((FooHandler)((AsyncResult)result).AsyncDelegate).EndInvoke(result); this.textBox1.Invoke((FooHandler)(() => this.textBox1.Text = "Done")); }, null); 
     } 

Control.Invoke đảm bảo đại biểu đó sẽ được thực hiện trong thread UI (nơi cửa sổ cha mẹ mô tả chính tồn tại)

lẽ tồn tại các biến thể tốt hơn.

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