Sử dụng Ứng dụng.DoEvents ()


272

Có thể Application.DoEvents()được sử dụng trong C #?

Đây có phải là một cách để cho phép GUI bắt kịp với phần còn lại của ứng dụng, giống như cách mà VB6 DoEventsthực hiện không?


35
DoEventslà một phần của Windows Forms, không phải ngôn ngữ C #. Như vậy, nó có thể được sử dụng từ bất kỳ ngôn ngữ .NET nào. Tuy nhiên, nó không nên được sử dụng từ bất kỳ ngôn ngữ .NET nào.
Stephen Cleary

3
Đó là năm 2017. Sử dụng Async / Await. Xem phần cuối của câu trả lời @hansPassant để biết chi tiết.
FastAl

Câu trả lời:


468

Hmya, bí ẩn lâu dài của DoEvents (). Đã có một lượng lớn phản ứng dữ dội chống lại nó, nhưng không ai thực sự giải thích tại sao nó "xấu". Loại trí tuệ tương tự như "không làm thay đổi cấu trúc". Erm, tại sao thời gian chạy và ngôn ngữ hỗ trợ thay đổi cấu trúc nếu điều đó rất tệ? Lý do tương tự: bạn tự bắn vào chân mình nếu bạn không làm đúng. Dễ dàng. Và làm điều đó đúng đòi hỏi phải biết chính xác những gì nó làm, trong trường hợp DoEvents () chắc chắn không dễ để mò mẫm.

Ngay lập tức: hầu như bất kỳ chương trình Windows Forms nào thực sự có lệnh gọi DoEvents (). Nó được ngụy trang khéo léo, tuy nhiên với một tên khác: ShowDialog (). Đó là DoEvents () cho phép một hộp thoại ở dạng modal mà không bị đóng băng phần còn lại của các cửa sổ trong ứng dụng.

Hầu hết các lập trình viên muốn sử dụng DoEvents để ngăn giao diện người dùng của họ bị đóng băng khi họ viết vòng lặp phương thức của riêng họ. Nó chắc chắn làm điều đó; nó gửi tin nhắn Windows và nhận bất kỳ yêu cầu sơn nào được gửi. Tuy nhiên, vấn đề là nó không chọn lọc. Nó không chỉ gửi tin nhắn sơn, nó còn cung cấp mọi thứ khác.

Và có một bộ thông báo gây rắc rối. Họ đến từ khoảng 3 feet trước màn hình. Ví dụ, người dùng có thể đóng cửa sổ chính trong khi vòng lặp gọi DoEvents () đang chạy. Điều đó hoạt động, giao diện người dùng đã biến mất. Nhưng mã của bạn không dừng lại, nó vẫn đang thực thi vòng lặp. Điều đó thật xấu. Rất, rất tệ.

Còn nữa: Người dùng có thể nhấp vào cùng một mục menu hoặc nút khiến cùng một vòng lặp bắt đầu. Bây giờ bạn có hai vòng lặp lồng nhau thực thi DoEvents (), vòng lặp trước bị treo và vòng lặp mới bắt đầu từ đầu. Điều đó có thể làm việc, nhưng cậu bé tỷ lệ cược là mỏng. Đặc biệt là khi vòng lặp lồng nhau kết thúc và vòng treo được nối lại, cố gắng hoàn thành một công việc đã hoàn thành. Nếu đó không phải là một quả bom với một ngoại lệ thì chắc chắn dữ liệu sẽ bị xáo trộn hoàn toàn.

Quay lại ShowDialog (). Nó thực thi DoEvents (), nhưng lưu ý rằng nó làm một cái gì đó khác. Nó vô hiệu hóa tất cả các cửa sổ trong ứng dụng , trừ hộp thoại. Bây giờ vấn đề 3 chân đã được giải quyết, người dùng không thể làm gì để làm rối logic. Cả hai chế độ thất bại đóng cửa sổ và khởi động lại công việc đều được giải quyết. Hoặc nói cách khác, không có cách nào để người dùng làm cho chương trình của bạn chạy mã theo một thứ tự khác. Nó sẽ thực thi dự đoán, giống như khi bạn kiểm tra mã của mình. Nó làm cho các hộp thoại cực kỳ khó chịu; Ai không ghét việc hộp thoại hoạt động và không thể sao chép và dán nội dung nào đó từ cửa sổ khác? Nhưng đó là giá.

Đó là những gì nó cần để sử dụng DoEvents một cách an toàn trong mã của bạn. Đặt thuộc tính Kích hoạt của tất cả các biểu mẫu của bạn thành false là cách nhanh chóng và hiệu quả để tránh các sự cố. Tất nhiên, không có lập trình viên nào thực sự thích làm điều này. Và không. Đó là lý do tại sao bạn không nên sử dụng DoEvents (). Bạn nên sử dụng chủ đề. Mặc dù họ trao cho bạn một kho vũ khí hoàn chỉnh để bắn vào chân bạn theo những cách đầy màu sắc và khó hiểu. Nhưng với lợi thế là bạn chỉ tự bắn vào chân mình; nó sẽ không (thường) cho phép người dùng bắn cô ấy.

Các phiên bản tiếp theo của C # và VB.NET sẽ cung cấp một khẩu súng khác với các từ khóa await và async mới. Lấy cảm hứng một phần nhỏ bởi những rắc rối gây ra bởi DoEvents và các luồng nhưng phần lớn là do thiết kế API của WinRT yêu cầu bạn phải cập nhật giao diện người dùng của mình trong khi hoạt động không đồng bộ đang diễn ra. Giống như đọc từ một tập tin.


1
Tuy nhiên, đây chỉ là phần nổi của tảng băng. Tôi đã sử dụng Application.DoEventsmà không gặp vấn đề gì cho đến khi tôi thêm một số tính năng UDP vào mã của mình, điều này dẫn đến sự cố được mô tả ở đây . Tôi rất muốn biết nếu có một con đường xung quanh đó với DoEvents.
darda

Đây là một câu trả lời hay, điều duy nhất tôi cảm thấy nó bỏ lỡ là một lời giải thích / thảo luận về việc thực hiện và thiết kế an toàn luồng hoặc nói chung - nhưng điều đó dễ dàng hơn gấp ba lần văn bản. :)
jheriko

5
Sẽ được hưởng lợi từ một giải pháp thực tế hơn là "sử dụng chủ đề" nói chung. Ví dụ, BackgroundWorkerthành phần này quản lý các luồng cho bạn, tránh hầu hết các kết quả đầy màu sắc của chụp chân và nó không yêu cầu các phiên bản ngôn ngữ C # xuất sắc.
Ben Voigt

29

Nó có thể, nhưng đó là một hack.

Xem DoEvents Evil? .

Trực tiếp từ trang MSDNthedev đã tham chiếu:

Gọi phương thức này làm cho luồng hiện tại bị treo trong khi tất cả các thông báo cửa sổ chờ được xử lý. Nếu một thông báo gây ra một sự kiện được kích hoạt, thì các khu vực khác trong mã ứng dụng của bạn có thể thực thi. Điều này có thể khiến ứng dụng của bạn thể hiện những hành vi bất ngờ khó gỡ lỗi. Nếu bạn thực hiện các hoạt động hoặc tính toán mất nhiều thời gian, thường nên thực hiện các hoạt động đó trên một luồng mới. Để biết thêm thông tin về lập trình không đồng bộ, hãy xem Tổng quan về lập trình không đồng bộ.

Vì vậy, Microsoft cảnh báo chống lại việc sử dụng nó.

Ngoài ra, tôi coi đó là một hack vì hành vi của nó là không thể đoán trước và dễ bị tác dụng phụ (điều này xuất phát từ kinh nghiệm cố gắng sử dụng DoEvents thay vì quay lên một chủ đề mới hoặc sử dụng công cụ nền).

Không có máy móc ở đây - nếu nó hoạt động như một giải pháp mạnh mẽ, tôi sẽ làm tất cả. Tuy nhiên, việc cố gắng sử dụng DoEvents trong .NET đã khiến tôi không có gì ngoài đau đớn.


1
Điều đáng chú ý là bài đăng đó có từ năm 2004, trước .NET 2.0 và BackgroundWorkerđã giúp đơn giản hóa cách "chính xác".
Justin

Đã đồng ý. Ngoài ra, thư viện tác vụ trong .NET 4.0 cũng khá đẹp.
RQDQ

1
Tại sao nó sẽ là một hack nếu Microsoft đã cung cấp một chức năng cho mục đích mà nó thường cần thiết?
Craig Johnston

1
@Craig Johnston - đã cập nhật câu trả lời của tôi để chi tiết hơn về lý do tại sao tôi tin rằng DoEvents rơi vào danh mục tin tặc.
RQDQ

Bài báo kinh dị mã hóa đó gọi nó là "DoEvents spackle." Xuất sắc!
gonzobrains

24

Có, có một phương thức DoEvents tĩnh trong lớp Ứng dụng trong không gian tên System.Windows.Forms. System.Windows.Forms.Application.DoEvents () có thể được sử dụng để xử lý các tin nhắn đang chờ trong hàng đợi trên luồng UI khi thực hiện một tác vụ chạy dài trong luồng UI. Điều này có lợi ích là làm cho UI có vẻ nhạy hơn và không bị "khóa" trong khi một tác vụ dài đang chạy. Tuy nhiên, điều này hầu như luôn luôn KHÔNG phải là cách tốt nhất để làm việc. Theo Microsoft gọi DoEvents "... khiến luồng hiện tại bị treo trong khi tất cả các thông báo cửa sổ chờ được xử lý." Nếu một sự kiện được kích hoạt, có khả năng xảy ra các lỗi không mong muốn và không liên tục, rất khó để theo dõi. Nếu bạn có một nhiệm vụ rộng rãi thì tốt hơn là thực hiện nó trong một luồng riêng biệt. Chạy các tác vụ dài trong một luồng riêng biệt cho phép chúng được xử lý mà không can thiệp vào giao diện người dùng tiếp tục chạy trơn tru. Nhìnở đây để biết thêm chi tiết.

Dưới đây là một ví dụ về cách sử dụng DoEvents; lưu ý rằng Microsoft cũng cung cấp một cảnh báo chống lại việc sử dụng nó.


13

Từ kinh nghiệm của mình, tôi sẽ khuyên bạn rất cẩn thận khi sử dụng DoEvents trong .NET. Tôi đã trải nghiệm một số kết quả rất lạ khi sử dụng DoEvents trong TabControl chứa DataGridViews. Mặt khác, nếu tất cả những gì bạn đang xử lý là một hình thức nhỏ với thanh tiến trình thì có thể sẽ ổn.

Điểm mấu chốt là: nếu bạn định sử dụng DoEvents, thì bạn cần kiểm tra kỹ lưỡng trước khi triển khai ứng dụng của mình.


Câu trả lời hay, nhưng nếu tôi có thể đề xuất một giải pháp cho điều tiến bộ tốt hơn , tôi sẽ nói, hãy thực hiện công việc của bạn trong một luồng riêng biệt, làm cho chỉ báo tiến trình của bạn có sẵn trong một biến dễ thay đổi, lồng vào nhau và làm mới thanh tiến trình của bạn từ bộ đếm thời gian . Bằng cách này, không có lập trình viên bảo trì nào muốn thêm mã nặng vào vòng lặp của bạn.
mg30rg

1
DoEvents hoặc tương đương là không thể tránh khỏi nếu bạn có quá trình UI quá lớn và không muốn khóa UI. Tùy chọn đầu tiên là không thực hiện các khối xử lý UI lớn, nhưng điều này có thể làm cho mã ít được bảo trì hơn. Tuy nhiên, đang chờ đợi Dispatcher.Yield () thực hiện một điều rất giống với DoEvents và về cơ bản có thể cho phép một quy trình UI khóa màn hình được thực hiện cho tất cả các ý định và mục đích không đồng bộ.
Nhà phát triển Melbourne

11

Đúng.

Tuy nhiên, nếu bạn cần sử dụng Application.DoEvents, đây chủ yếu là một dấu hiệu của một thiết kế ứng dụng tồi. Có lẽ bạn muốn làm một số công việc trong một chủ đề riêng thay thế?


3
Điều gì nếu bạn muốn nó để bạn có thể quay và chờ đợi để hoàn thành trong một chủ đề khác?
jheriko

@jheriko Sau đó, bạn thực sự nên thử async-await.
mg30rg

5

Tôi đã thấy nhận xét của jheriko ở trên và ban đầu đồng ý rằng tôi không thể tìm cách tránh sử dụng DoEvents nếu cuối cùng bạn quay chuỗi giao diện người dùng chính của mình để chờ một đoạn mã không đồng bộ chạy dài trên một luồng khác hoàn thành. Nhưng từ câu trả lời của Matthias, việc làm mới một bảng điều khiển nhỏ trên giao diện người dùng của tôi có thể thay thế DoEvents (và tránh tác dụng phụ khó chịu).

Chi tiết hơn về trường hợp của tôi ...

Tôi đã làm như sau (như được đề xuất ở đây ) để đảm bảo rằng màn hình giới thiệu loại thanh tiến trình ( Cách hiển thị lớp phủ "đang tải" ... ) được cập nhật trong một lệnh SQL chạy dài:

IAsyncResult asyncResult = sqlCmd.BeginExecuteNonQuery();
while (!asyncResult.IsCompleted)  //UI thread needs to Wait for Async SQL command to return
{
      System.Threading.Thread.Sleep(10); 
      Application.DoEvents();  //to make the UI responsive
}

Nhược điểm: Đối với tôi, gọi DoEvents có nghĩa là các lần nhấp chuột đôi khi bắn vào các biểu mẫu phía sau màn hình giật gân của tôi, ngay cả khi tôi đã tạo ra TopMost.

Câu trả lời hay: Thay thế dòng DoEvents bằng một cuộc gọi Làm mới đơn giản thành một bảng nhỏ ở giữa màn hình giật gân của tôi , FormSplash.Panel1.Refresh(). Giao diện người dùng cập nhật độc đáo và sự kỳ lạ của DoEvents mà những người khác đã cảnh báo đã biến mất.


3
Làm mới không cập nhật cửa sổ, mặc dù. Nếu người dùng chọn một cửa sổ khác trên màn hình nền, nhấp trở lại cửa sổ của bạn sẽ không có bất kỳ ảnh hưởng nào và HĐH sẽ liệt kê ứng dụng của bạn là không phản hồi. DoEvents () thực hiện nhiều hơn là làm mới, vì nó tương tác với HĐH thông qua hệ thống nhắn tin.
ThunderGr

4

Tôi đã thấy nhiều ứng dụng thương mại, sử dụng "DoEvents-Hack". Đặc biệt là khi kết xuất đồ chơi, tôi thường thấy điều này:

while(running)
{
    Render();
    Application.DoEvents();
}

Họ đều biết về sự xấu xa của phương pháp đó. Tuy nhiên, họ sử dụng hack, vì họ không biết bất kỳ giải pháp nào khác. Dưới đây là một số cách tiếp cận được lấy từ một bài đăng trên blog của Tom Miller :

  • Đặt biểu mẫu của bạn để có tất cả các bản vẽ xảy ra trong WmPaint và thực hiện kết xuất của bạn ở đó. Trước khi kết thúc phương thức OnPaint, hãy đảm bảo bạn thực hiện việc này.Invalidate (); Điều này sẽ khiến phương thức OnPaint bị loại bỏ ngay lập tức.
  • P / Gọi vào API Win32 và gọi PeekMessage / TranslateMessage / DispatchMessage. (Doevents thực sự làm một cái gì đó tương tự, nhưng bạn có thể làm điều này mà không cần phân bổ thêm).
  • Viết lớp biểu mẫu của riêng bạn, đó là một trình bao bọc nhỏ xung quanh CreateWindowEx và tự kiểm soát hoàn toàn vòng lặp thông báo. -Decide rằng phương pháp DoEvents hoạt động tốt cho bạn và gắn bó với nó.


3

DoEvents cho phép người dùng nhấp xung quanh hoặc nhập và kích hoạt các sự kiện khác, và các chủ đề nền là một cách tiếp cận tốt hơn.

Tuy nhiên, vẫn có những trường hợp bạn có thể gặp phải các vấn đề yêu cầu xóa thông báo sự kiện. Tôi gặp phải một vấn đề trong đó điều khiển RichTextBox đã bỏ qua phương thức ScrollToCaret () khi điều khiển có các thông báo trong hàng đợi để xử lý.

Đoạn mã sau chặn tất cả đầu vào của người dùng trong khi thực hiện DoEvents:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Integrative.Desktop.Common
{
    static class NativeMethods
    {
        #region Block input

        [DllImport("user32.dll", EntryPoint = "BlockInput")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool BlockInput([MarshalAs(UnmanagedType.Bool)] bool fBlockIt);

        public static void HoldUser()
        {
            BlockInput(true);
        }

        public static void ReleaseUser()
        {
            BlockInput(false);
        }

        public static void DoEventsBlockingInput()
        {
            HoldUser();
            Application.DoEvents();
            ReleaseUser();
        }

        #endregion
    }
}

1
Bạn nên luôn luôn chặn các sự kiện khi gọi doevents. Nếu không, các sự kiện khác trên ứng dụng của bạn sẽ phản hồi và ứng dụng của bạn có thể bắt đầu thực hiện hai việc cùng một lúc.
FastAl

2

Application.DoEvents có thể tạo ra sự cố, nếu một cái gì đó ngoài xử lý đồ họa được đặt trong hàng đợi tin nhắn.

Nó có thể hữu ích cho việc cập nhật các thanh tiến trình và thông báo cho người dùng về tiến trình trong một cái gì đó như xây dựng và tải MainForm, nếu mất một thời gian.

Trong một ứng dụng gần đây tôi đã tạo, tôi đã sử dụng DoEvents để cập nhật một số nhãn trên Màn hình tải mỗi khi một khối mã được thực thi trong hàm tạo của MainForm của tôi. Trong trường hợp này, luồng UI được sử dụng để gửi email trên máy chủ SMTP không hỗ trợ các cuộc gọi SendAsync (). Tôi có thể đã tạo một luồng khác với các phương thức Begin () và End () và được gọi là Send () từ chúng, nhưng phương thức đó dễ bị lỗi và tôi thích Mẫu chính của ứng dụng của mình không ném ngoại lệ trong quá trình xây dựng.

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.