2009-02-13 53 views
45

Có phương pháp đơn giản nào để trì hoãn cuộc gọi chức năng trong khi cho phép chuỗi tiếp tục thực hiện không?Các cuộc gọi chức năng bị trì hoãn

ví dụ:

public void foo() 
{ 
    // Do stuff! 

    // Delayed call to bar() after x number of ms 

    // Do more Stuff 
} 

public void bar() 
{ 
    // Only execute once foo has finished 
} 

Tôi biết rằng điều này có thể đạt được bằng cách sử dụng bộ hẹn giờ và xử lý sự kiện, nhưng tôi đã tự hỏi liệu có một cách C# tiêu chuẩn để đạt được điều này không?

Nếu có ai tò mò, lý do là điều này là foo() và bar() là các lớp khác nhau (singleton) mà tôi cần phải gọi cho nhau trong những trường hợp ngoại lệ. Vấn đề là điều này được thực hiện lúc khởi tạo nên foo cần phải gọi thanh cần một thể hiện của lớp foo đang được tạo ... do đó cuộc gọi bị trì hoãn tới bar() để đảm bảo rằng foo hoàn toàn được instanciated .. Đọc lại hầu hết các thiết kế xấu!

EDIT

Tôi sẽ đưa các điểm về thiết kế xấu dưới advisement! Tôi đã nghĩ rằng tôi có thể cải thiện hệ thống, tuy nhiên, tình huống khó chịu này chỉ chỉ xảy ra khi một ngoại lệ được ném, tại tất cả các lần khác hai đơn cùng tồn tại rất độc đáo. Tôi nghĩ rằng tôi sẽ không lộn xộn với những kẻ xấu hổ khó chịu, thay vào đó tôi sẽ tái cấu trúc lại việc khởi tạo một trong các lớp học.

+0

Bạn cần phải sửa chữa nó nhưng không phải bằng cách sử dụng chủ đề (hoặc bất kỳ thực hành asyn khác cho rằng vấn đề) – ShuggyCoUk

+0

Có phải sử dụng đề để đồng bộ hóa đối tượng khởi tạo là dấu hiệu cho thấy bạn nên đi theo một cách khác. Dàn nhạc có vẻ là một lựa chọn tốt hơn. – thinkbeforecoding

+0

Phục sinh! - Nhận xét về thiết kế, bạn có thể lựa chọn để khởi tạo hai giai đoạn. Vẽ từ API Unity3D, có các giai đoạn 'Awake' và' Start'. Trong giai đoạn 'Awake', bạn cấu hình chính mình, và vào cuối giai đoạn này tất cả các đối tượng được khởi tạo. Trong giai đoạn 'Start', các đối tượng có thể bắt đầu giao tiếp với nhau. – cod3monk3y

Trả lời

8

Có vẻ như việc kiểm soát việc tạo ra cả hai đối tượng này và nhu cầu phụ thuộc lẫn nhau của chúng được kiểm soát bên ngoài, thay vì giữa các lớp.

+0

+1, điều này nghe có vẻ như bạn cần một dàn nhạc của một số loại và có lẽ là một nhà máy – ng5000

1

Vâng, tôi sẽ phải đồng ý với "thiết kế" điểm ... nhưng có lẽ bạn có thể sử dụng một màn hình để cho ai biết khi người kia là quá khứ phần quan trọng ...

public void foo() { 
     // Do stuff! 

     object syncLock = new object(); 
     lock (syncLock) { 
      // Delayed call to bar() after x number of ms 
      ThreadPool.QueueUserWorkItem(delegate { 
       lock(syncLock) { 
        bar(); 
       } 
      }); 

      // Do more Stuff 
     } 
     // lock now released, bar can begin    
    } 
0

Không có cách tiêu chuẩn nào để trì hoãn cuộc gọi đến một chức năng khác ngoài việc sử dụng bộ hẹn giờ và sự kiện.

Điều này nghe giống như kiểu chống GUI của việc trì hoãn cuộc gọi đến một phương thức để bạn có thể chắc chắn rằng biểu mẫu đã hoàn tất việc đặt. Không phải là một ý tưởng tốt.

5

Nó thực sự là một thiết kế rất xấu, hãy để một mình singleton của chính nó là thiết kế xấu.

Tuy nhiên, nếu bạn thực sự cần phải trì hoãn thực hiện, đây là những gì bạn có thể làm:

BackgroundWorker barInvoker = new BackgroundWorker(); 
barInvoker.DoWork += delegate 
    { 
     Thread.Sleep(TimeSpan.FromSeconds(1)); 
     bar(); 
    }; 
barInvoker.RunWorkerAsync(); 

di chúc này, tuy nhiên, gọi bar() trên một sợi riêng biệt. Nếu bạn cần gọi số bar() trong chuỗi ban đầu, bạn có thể cần phải di chuyển yêu cầu bar() tới trình xử lý RunWorkerCompleted hoặc thực hiện một chút hack với SynchronizationContext.

78

Tôi đã tìm kiếm thứ gì đó như thế này - tôi đã nghĩ ra những điều sau đây, mặc dù nó sử dụng bộ hẹn giờ, nó chỉ sử dụng một lần cho độ trễ ban đầu và không yêu cầu bất kỳ cuộc gọi nào Sleep ...

public void foo() 
{ 
    System.Threading.Timer timer = null; 
    timer = new System.Threading.Timer((obj) => 
        { 
         bar(); 
         timer.Dispose(); 
        }, 
       null, 1000, System.Threading.Timeout.Infinite); 
} 

public void bar() 
{ 
    // do stuff 
} 

(nhờ Fred Deschenes cho ý tưởng của xử lý bộ đếm thời gian trong callback)

+2

Tôi cảm thấy đây là câu trả lời hay nhất nói chung để trì hoãn một cuộc gọi chức năng. Không có chủ đề, không có nền hoạt động, không ngủ. Timer rất hiệu quả và bộ nhớ/cpu khôn ngoan. – Zyo

+1

@Zyo, cảm ơn nhận xét của bạn - có bộ hẹn giờ hiệu quả và sự chậm trễ này hữu ích trong nhiều trường hợp, đặc biệt là khi giao tiếp với một thứ ngoài tầm kiểm soát của bạn - không có bất kỳ hỗ trợ nào cho sự kiện thông báo. –

+0

Khi nào bạn bỏ hẹn giờ? –

2
public static class DelayedDelegate 
{ 

    static Timer runDelegates; 
    static Dictionary<MethodInvoker, DateTime> delayedDelegates = new Dictionary<MethodInvoker, DateTime>(); 

    static DelayedDelegate() 
    { 

     runDelegates = new Timer(); 
     runDelegates.Interval = 250; 
     runDelegates.Tick += RunDelegates; 
     runDelegates.Enabled = true; 

    } 

    public static void Add(MethodInvoker method, int delay) 
    { 

     delayedDelegates.Add(method, DateTime.Now + TimeSpan.FromSeconds(delay)); 

    } 

    static void RunDelegates(object sender, EventArgs e) 
    { 

     List<MethodInvoker> removeDelegates = new List<MethodInvoker>(); 

     foreach (MethodInvoker method in delayedDelegates.Keys) 
     { 

      if (DateTime.Now >= delayedDelegates[method]) 
      { 
       method(); 
       removeDelegates.Add(method); 
      } 

     } 

     foreach (MethodInvoker method in removeDelegates) 
     { 

      delayedDelegates.Remove(method); 

     } 


    } 

} 

Cách sử dụng:

DelayedDelegate.Add(MyMethod,5); 

void MyMethod() 
{ 
    MessageBox.Show("5 Seconds Later!"); 
} 
+1

Tôi khuyên bạn nên đặt một số logic để tránh bộ đếm thời gian chạy mỗi 250 mili giây. Đầu tiên: Bạn có thể tăng độ trễ lên 500 mili giây vì khoảng thời gian tối thiểu cho phép của bạn là 1 giây. Thứ hai: Bạn chỉ có thể bắt đầu hẹn giờ khi các đại biểu mới được thêm vào và dừng nó khi không còn đại biểu nữa. Không có lý do để tiếp tục sử dụng chu kỳ CPU khi không có gì để làm. Thứ ba: bạn có thể đặt khoảng thời gian hẹn giờ thành độ trễ tối thiểu trên tất cả các đại biểu. Vì vậy, nó thức dậy chỉ khi nó cần phải gọi một đại biểu, thay vì thức dậy mỗi 250 mili giây để xem nếu có cái gì đó để làm. –

+0

MethodInvoker là một đối tượng Windows.Forms. Có cách nào khác cho các nhà phát triển web không? tức là: cái gì đó không đụng độ với System.Web.UI.WebControls. – Fandango68

1

tôi mặc dù giải pháp hoàn hảo sẽ có một bộ đếm thời gian xử lý hành động bị trì hoãn. FxCop không thích khi bạn có một khoảng thời gian ít hơn một giây. Tôi cần phải trì hoãn hành động của mình cho đến SAU KHI DataGrid của tôi đã hoàn thành sắp xếp theo cột. Tôi đã tìm ra bộ hẹn giờ một lần (AutoReset = false) sẽ là giải pháp và nó hoạt động hoàn hảo. VÀ, FxCop sẽ không cho phép tôi chặn cảnh báo!

0

xây dựng trên câu trả lời từ David O'Donoghue đây là một phiên bản tối ưu hóa của các đại biểu Chậm:

using System.Windows.Forms; 
using System.Collections.Generic; 
using System; 

namespace MyTool 
{ 
    public class DelayedDelegate 
    { 
     static private DelayedDelegate _instance = null; 

     private Timer _runDelegates = null; 

     private Dictionary<MethodInvoker, DateTime> _delayedDelegates = new Dictionary<MethodInvoker, DateTime>(); 

     public DelayedDelegate() 
     { 
     } 

     static private DelayedDelegate Instance 
     { 
      get 
      { 
       if (_instance == null) 
       { 
        _instance = new DelayedDelegate(); 
       } 

       return _instance; 
      } 
     } 

     public static void Add(MethodInvoker pMethod, int pDelay) 
     { 
      Instance.AddNewDelegate(pMethod, pDelay * 1000); 
     } 

     public static void AddMilliseconds(MethodInvoker pMethod, int pDelay) 
     { 
      Instance.AddNewDelegate(pMethod, pDelay); 
     } 

     private void AddNewDelegate(MethodInvoker pMethod, int pDelay) 
     { 
      if (_runDelegates == null) 
      { 
       _runDelegates = new Timer(); 
       _runDelegates.Tick += RunDelegates; 
      } 
      else 
      { 
       _runDelegates.Stop(); 
      } 

      _delayedDelegates.Add(pMethod, DateTime.Now + TimeSpan.FromMilliseconds(pDelay)); 

      StartTimer(); 
     } 

     private void StartTimer() 
     { 
      if (_delayedDelegates.Count > 0) 
      { 
       int delay = FindSoonestDelay(); 
       if (delay == 0) 
       { 
        RunDelegates(); 
       } 
       else 
       { 
        _runDelegates.Interval = delay; 
        _runDelegates.Start(); 
       } 
      } 
     } 

     private int FindSoonestDelay() 
     { 
      int soonest = int.MaxValue; 
      TimeSpan remaining; 

      foreach (MethodInvoker invoker in _delayedDelegates.Keys) 
      { 
       remaining = _delayedDelegates[invoker] - DateTime.Now; 
       soonest = Math.Max(0, Math.Min(soonest, (int)remaining.TotalMilliseconds)); 
      } 

      return soonest; 
     } 

     private void RunDelegates(object pSender = null, EventArgs pE = null) 
     { 
      try 
      { 
       _runDelegates.Stop(); 

       List<MethodInvoker> removeDelegates = new List<MethodInvoker>(); 

       foreach (MethodInvoker method in _delayedDelegates.Keys) 
       { 
        if (DateTime.Now >= _delayedDelegates[method]) 
        { 
         method(); 

         removeDelegates.Add(method); 
        } 
       } 

       foreach (MethodInvoker method in removeDelegates) 
       { 
        _delayedDelegates.Remove(method); 
       } 
      } 
      catch (Exception ex) 
      { 
      } 
      finally 
      { 
       StartTimer(); 
      } 
     } 
    } 
} 

Lớp có thể được nhẹ hơn được cải thiện bằng cách sử dụng một chìa khóa duy nhất cho các đại biểu. Vì nếu bạn thêm cùng một đại biểu lần thứ hai trước lần đầu tiên được kích hoạt, bạn có thể gặp sự cố với từ điển.

0
private static volatile List<System.Threading.Timer> _timers = new List<System.Threading.Timer>(); 
     private static object lockobj = new object(); 
     public static void SetTimeout(Action action, int delayInMilliseconds) 
     { 
      System.Threading.Timer timer = null; 
      var cb = new System.Threading.TimerCallback((state) => 
      { 
       lock (lockobj) 
        _timers.Remove(timer); 
       timer.Dispose(); 
       action() 
      }); 
      lock (lockobj) 
       _timers.Add(timer = new System.Threading.Timer(cb, null, delayInMilliseconds, System.Threading.Timeout.Infinite)); 
} 
14

Ngoài việc đồng ý với quan sát thiết kế của người nhận xét trước, không có giải pháp nào đủ sạch cho tôi. Net 4 cung cấp DispatcherTask lớp mà làm trì hoãn thực hiện trên thread hiện khá đơn giản:

static class AsyncUtils 
{ 
    static public void DelayCall(int msec, Action fn) 
    { 
     // Grab the dispatcher from the current executing thread 
     Dispatcher d = Dispatcher.CurrentDispatcher; 

     // Tasks execute in a thread pool thread 
     new Task (() => { 
      System.Threading.Thread.Sleep (msec); // delay 

      // use the dispatcher to asynchronously invoke the action 
      // back on the original thread 
      d.BeginInvoke (fn);      
     }).Start(); 
    } 
} 

Đối với bối cảnh, tôi đang sử dụng này để debounce một ICommand gắn với một nút chuột trái lên trên một giao diện người dùng thành phần. Người dùng đang nhấp đúp vào đó đã gây ra tất cả các loại havoc. (Tôi biết tôi cũng có thể sử dụng các trình xử lý Click/DoubleClick, nhưng tôi muốn một giải pháp hoạt động với ICommand s trên bảng).

public void Execute(object parameter) 
{ 
    if (!IsDebouncing) { 
     IsDebouncing = true; 
     AsyncUtils.DelayCall (DebouncePeriodMsec,() => { 
      IsDebouncing = false; 
     }); 

     _execute(); 
    } 
} 
+0

Cách tuyệt vời để thực thi trên cùng một chuỗi. Cám ơn vì cái này! –

73

Nhờ hiện đại C# 5/6 :)

public void foo() 
{ 
    Task.Delay(1000).ContinueWith(t=> bar()); 
} 

public void bar() 
{ 
    // do stuff 
} 
+5

Câu trả lời này thật tuyệt vời vì 2 lý do. Mã đơn giản và thực tế là sự chậm trễ KHÔNG tạo ra một chủ đề cũng không sử dụng các hồ bơi thread như Task.Run khác hoặc Task.StartNew ... nó nội bộ một bộ đếm thời gian. – Zyo

+0

Một giải pháp tốt. – x4h1d

+2

Cũng lưu ý một phiên bản tương đương (IMO) hơi sạch hơn: Task.Delay (TimeSpan.FromSeconds (1)). ContinueWith (_ => bar()); – Taran

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