C # Timers có trôi qua trên một chuỗi riêng không?


97

System.Timers.Timer có trôi qua trên một luồng riêng biệt với luồng đã tạo ra nó không?

Giả sử tôi có một lớp học có bộ đếm thời gian hoạt động 5 giây một lần. Khi bộ hẹn giờ kích hoạt, trong phương thức đã trôi qua, một số đối tượng được sửa đổi. Giả sử sẽ mất nhiều thời gian để sửa đổi đối tượng này, chẳng hạn như 10 giây. Có khả năng là tôi sẽ gặp xung đột luồng trong trường hợp này không?


Điều này có thể dẫn đến các vấn đề. Lưu ý rằng nói chung, các luồng threadpool không được thiết kế cho các quy trình chạy lâu dài.
Greg D

Tôi đã chạy vào điều này, thử nghiệm với một dịch vụ windows. Điều phù hợp với tôi là vô hiệu hóa bộ hẹn giờ dưới dạng hướng dẫn đầu tiên trong sự kiện OnTimer, thực hiện các tác vụ của tôi, sau đó bật bộ hẹn giờ ở cuối. Điều này đã hoạt động đáng tin cậy trong môi trường sản xuất trong một thời gian.
Steve

Câu trả lời:


60

Đối với System.Timers.Timer :

Xem câu trả lời của Brian Gideon bên dưới

Đối với System.Threading.Timer :

Tài liệu MSDN về Bộ hẹn giờ cho biết:

Lớp System.Threading.Timer thực hiện các cuộc gọi lại trên một luồng ThreadPool và hoàn toàn không sử dụng mô hình sự kiện.

Vì vậy, thực sự bộ hẹn giờ trôi qua trên một chuỗi khác.


21
Đúng, nhưng đó là một lớp hoàn toàn khác. OP hỏi về lớp System.Timers.Timer.
Brian Gideon

1
Oh bạn nói đúng. msdn.microsoft.com/en-us/library/system.timers.timer.aspx cho biết "Sự kiện đã qua được đưa ra trên một chuỗi ThreadPool." Tôi cho rằng kết luận tương tự từ đó.
Joren 17/09/09

6
Vâng, vâng, nhưng nó không hoàn toàn đơn giản. Hãy xem câu trả lời của tôi.
Brian Gideon 17/09/09

192

Nó phụ thuộc. Có System.Timers.Timerhai chế độ hoạt động.

Nếu SynchronizingObjectđược đặt thành một ISynchronizeInvokephiên bản thì Elapsedsự kiện sẽ thực thi trên chuỗi lưu trữ đối tượng đồng bộ hóa. Thông thường những ISynchronizeInvokephiên bản này không phải là phiên bản cũ Controlvà đơn giản Formmà chúng ta đã quen thuộc. Vì vậy, trong trường hợp đó, Elapsedsự kiện được gọi trên chuỗi giao diện người dùng và nó hoạt động tương tự như System.Windows.Forms.Timer. Nếu không, nó thực sự phụ thuộc vào ISynchronizeInvoketrường hợp cụ thể đã được sử dụng.

Nếu SynchronizingObjectlà null thì Elapsedsự kiện được gọi trên một ThreadPoolluồng và nó hoạt động tương tự như System.Threading.Timer. Trên thực tế, nó thực sự sử dụng một System.Threading.Timerhậu trường và thực hiện hoạt động điều phối sau khi nó nhận được lệnh gọi lại hẹn giờ nếu cần.


4
Nếu bạn muốn lệnh gọi lại của bộ hẹn giờ thực thi trên một luồng mới, bạn nên sử dụng dấu System.Threading.Timerhoặc System.Timers.Timer?
CJ7

1
@ cj7: Một trong hai có thể làm điều đó.
Brian Gideon

và nếu tôi có một danh sách kiểu phức tạp (người) và muốn có thời gian bên trong mỗi người? Tôi cần điều này chạy trên cùng một chủ đề (tất cả mọi người), bởi vì nếu nó gọi phương thức người thứ nhất, thì người thứ hai phải đợi cho đến khi người đầu tiên kết thúc sự kiện đã trôi qua. Tôi có thể làm điều đó?
Leandro De Mello Fagundes

4
Mọi người ... System.Timers.Timercó hai phương thức hoạt động. Nó có thể chạy trên một chuỗi nhóm luồng được chỉ định ngẫu nhiên HOẶC nó có thể chạy trên bất kỳ luồng nào đang lưu trữ ISynchronizeInvokephiên bản. Tôi không biết làm thế nào để làm cho điều đó rõ ràng hơn. System.Threading.Timercó rất ít (nếu có) để làm với câu hỏi ban đầu.
Brian Gideon

@LeandroDeMelloFagundes Bạn không thể sử dụng lockcho điều đó?
Ozkan

24

Mỗi sự kiện đã trôi qua sẽ kích hoạt trong cùng một chuỗi trừ khi một sự kiện đã trôi qua trước đó vẫn đang chạy.

Vì vậy, nó xử lý va chạm cho bạn

thử đưa cái này vào bảng điều khiển

static void Main(string[] args)
{
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
    var timer = new Timer(1000);
    timer.Elapsed += timer_Elapsed;
    timer.Start();
    Console.ReadLine();
}

static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    Thread.Sleep(2000);
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
}

bạn sẽ nhận được một cái gì đó như thế này

10
6
12
6
12

trong đó 10 là luồng gọi và 6 và 12 đang kích hoạt từ sự kiện bg đã trôi qua. Nếu bạn loại bỏ Thread.Sleep (2000); bạn sẽ nhận được một cái gì đó như thế này

10
6
6
6
6

Vì không có va chạm.

Nhưng điều này vẫn để lại cho bạn một vấn đề. nếu bạn đang kích hoạt sự kiện cứ sau 5 giây và mất 10 giây để chỉnh sửa, bạn cần khóa để bỏ qua một số chỉnh sửa.


8
Việc thêm một timer.Stop()vào đầu phương thức sự kiện Đã qua và sau đó thêm một timer.Start()vào cuối phương thức sự kiện Đã trôi qua sẽ giữ cho sự kiện Đã qua không va chạm.
Metro Smurf

4
Bạn không cần đặt timer.Stop () bạn chỉ cần định nghĩa timer.AutoReset = false; sau đó bạn tạo timer.Start () sau khi bạn xử lý sự kiện. Tôi nghĩ đây là cách tốt hơn để bạn tránh va chạm.
João Antunes,

17

Đối với System.Timers.Timer, trên chuỗi riêng biệt, nếu SynchrofyingObject không được đặt.

    static System.Timers.Timer DummyTimer = null;

    static void Main(string[] args)
    {
        try
        {

            Console.WriteLine("Main Thread Id: " + System.Threading.Thread.CurrentThread.ManagedThreadId);

            DummyTimer = new System.Timers.Timer(1000 * 5); // 5 sec interval
            DummyTimer.Enabled = true;
            DummyTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnDummyTimerFired);
            DummyTimer.AutoReset = true;

            DummyTimer.Start();

            Console.WriteLine("Hit any key to exit");
            Console.ReadLine();
        }
        catch (Exception Ex)
        {
            Console.WriteLine(Ex.Message);
        }

        return;
    }

    static void OnDummyTimerFired(object Sender, System.Timers.ElapsedEventArgs e)
    {
        Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
        return;
    }

Đầu ra bạn sẽ thấy nếu DummyTimer được kích hoạt trong khoảng thời gian 5 giây:

Main Thread Id: 9
   12
   12
   12
   12
   12
   ... 

Vì vậy, như đã thấy, OnDummyTimerFosystem được thực thi trên chuỗi Công nhân.

Không, phức tạp hơn - Nếu bạn giảm khoảng thời gian xuống còn 10 ms,

Main Thread Id: 9
   11
   13
   12
   22
   17
   ... 

Điều này là do nếu quá trình thực thi OnDummyTimerFosystem trước đó không được thực hiện khi đánh dấu tiếp theo được kích hoạt, thì .NET sẽ tạo một luồng mới để thực hiện công việc này.

Làm phức tạp thêm mọi thứ, "Lớp System.Timers.Timer cung cấp một cách dễ dàng để giải quyết tình huống khó xử này — nó làm lộ thuộc tính SynchrofyingObject công khai. Đặt thuộc tính này thành một bản sao của Windows Form (hoặc một điều khiển trên Windows Form) sẽ đảm bảo rằng mã trong trình xử lý sự kiện đã qua của bạn chạy trên cùng một chuỗi mà trên đó SynchronizationObject đã được khởi tạo. "

http://msdn.microsoft.com/en-us/magazine/cc164015.aspx#S2


Đó là một ví dụ điển hình. Tôi không biết cho đến bây giờ và tôi cần phải cài đặt một số khóa ở đây và ở đó. Hoàn hảo.
Olaru Mircea

Và điều gì sẽ xảy ra nếu tôi muốn bộ hẹn giờ kích hoạt sự kiện Đã qua trong chuỗi chính của ứng dụng bảng điều khiển?
jacktric

13

Nếu sự kiện đã trôi qua diễn ra lâu hơn thì khoảng thời gian đó, nó sẽ tạo ra một chuỗi khác để nâng cao sự kiện đã trôi qua. Nhưng có một giải pháp cho việc này

static void timer_Elapsed(object sender, ElapsedEventArgs e)    
{     
   try
   {
      timer.Stop(); 
      Thread.Sleep(2000);        
      Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);    
   }
   finally
   {
     timer.Start();
   }
}

+1 Câu trả lời đơn giản và rõ ràng, nhưng điều này sẽ không luôn hoạt động. Thay vào đó, người ta nên sử dụng thuộc tính lock hoặc SynchronizationObject của bộ hẹn giờ.
RollerCosta
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.