2012-03-07 27 views
32

Tôi hiện đang viết chương trình đầu tiên của mình trên C# và tôi cực kỳ mới với ngôn ngữ (được sử dụng để chỉ làm việc với C cho đến nay). Tôi đã thực hiện rất nhiều nghiên cứu, nhưng tất cả các câu trả lời đều quá chung chung và tôi không thể làm việc đó được.Cách cập nhật giao diện người dùng từ một chuỗi khác đang chạy trong một lớp khác

Vì vậy, đây là vấn đề của tôi (rất phổ biến): Tôi có một ứng dụng WPF có đầu vào từ một vài hộp văn bản được người dùng điền và sau đó sử dụng để thực hiện nhiều phép tính với chúng. Họ sẽ mất khoảng 2-3 phút, vì vậy tôi muốn cập nhật một thanh tiến trình và một textblock cho tôi biết tình trạng hiện tại là gì. Ngoài ra tôi cần phải lưu trữ các đầu vào giao diện người dùng từ người dùng và đưa chúng vào luồng, vì vậy tôi có một lớp thứ ba, mà tôi sử dụng để tạo một đối tượng và muốn chuyển đối tượng này đến chủ đề nền. Rõ ràng là tôi sẽ chạy các phép tính trong một chuỗi khác, vì vậy giao diện người dùng không bị đóng băng, nhưng tôi không biết cách cập nhật giao diện người dùng vì tất cả các phương pháp tính toán là một phần của một lớp khác. Sau nhiều nghiên cứu, tôi nghĩ rằng phương pháp tốt nhất để sử dụng sẽ là sử dụng điều phối viên và TPL chứ không phải là người làm việc nền tảng, nhưng thành thật mà nói tôi không chắc họ làm việc như thế nào và sau khoảng 20 giờ dùng thử và lỗi với các câu trả lời khác, tôi quyết định tự đặt câu hỏi.

Dưới đây là một cấu trúc rất đơn giản của chương trình của tôi:

public partial class MainWindow : Window 
{ 
    public MainWindow() 
    { 
     Initialize Component(); 
    } 

    private void startCalc(object sender, RoutedEventArgs e) 
    { 
     inputValues input = new inputValues(); 

     calcClass calculations = new calcClass(); 

     try 
     { 
      input.pota = Convert.ToDouble(aVar.Text); 
      input.potb = Convert.ToDouble(bVar.Text); 
      input.potc = Convert.ToDouble(cVar.Text); 
      input.potd = Convert.ToDouble(dVar.Text); 
      input.potf = Convert.ToDouble(fVar.Text); 
      input.potA = Convert.ToDouble(AVar.Text); 
      input.potB = Convert.ToDouble(BVar.Text); 
      input.initStart = Convert.ToDouble(initStart.Text); 
      input.initEnd = Convert.ToDouble(initEnd.Text); 
      input.inita = Convert.ToDouble(inita.Text); 
      input.initb = Convert.ToDouble(initb.Text); 
      input.initc = Convert.ToDouble(initb.Text); 
     } 
     catch 
     { 
      MessageBox.Show("Some input values are not of the expected Type.", "Wrong Input", MessageBoxButton.OK, MessageBoxImage.Error); 
     } 
     Thread calcthread = new Thread(new ParameterizedThreadStart(calculations.testMethod); 
     calcthread.Start(input); 
    } 

public class inputValues 
{ 
    public double pota, potb, potc, potd, potf, potA, potB; 
    public double initStart, initEnd, inita, initb, initc; 
} 

public class calcClass 
{ 
    public void testmethod(inputValues input) 
    { 
     Thread.CurrentThread.Priority = ThreadPriority.Lowest; 
     int i; 
     //the input object will be used somehow, but that doesn't matter for my problem 
     for (i = 0; i < 1000; i++) 
     { 
      Thread.Sleep(10); 
     } 
    } 
} 

Tôi sẽ rất biết ơn nếu ai đó có một lời giải thích đơn giản như thế nào để cập nhật giao diện người dùng từ bên trong TestMethod. Kể từ khi tôi mới đến C# và lập trình hướng đối tượng, câu trả lời quá phức tạp tôi rất có thể sẽ không hiểu, tôi sẽ làm tốt nhất của tôi mặc dù.

Ngoài ra nếu ai đó có ý tưởng tốt hơn nói chung (có thể sử dụng nhân viên làm nền hoặc bất kỳ điều gì khác), tôi mở để xem.

+2

Đây là câu hỏi cũ và được trả lời, nhưng đủ phổ biến để tôi chia sẻ điều này cho bất kỳ ai muốn triển khai 'Trình báo cáo tiến độ' rất đơn giản giữa các chủ đề. Sử dụng lớp Progress . Việc thực hiện được chi tiết trong một bài viết hay của Stephan Cleary tại đây: http://blog.stephencleary.com/2012/02/reporting-progress-from-async-tasks.html. – SeanOB

Trả lời

52

Trước tiên, bạn cần sử dụng Dispatcher.Invoke để thay đổi giao diện người dùng từ một chuỗi khác và để thực hiện điều đó từ một lớp khác, bạn có thể sử dụng sự kiện.
Sau đó, bạn có thể đăng ký để sự kiện đó (s) trong những lớp học chính và sự nhanh nhẹn thay đổi đối với giao diện người dùng và trong lớp tính bạn ném sự kiện này khi bạn muốn thông báo giao diện người dùng:

class MainWindow 
{ 
    startCalc() 
    { 
     //your code 
     CalcClass calc = new CalcClass(); 
     calc.ProgressUpdate += (s, e) => { 
      Dispatcher.Invoke((Action)delegate() { /* update UI */ }); 
     }; 
     Thread calcthread = new Thread(new ParameterizedThreadStart(calc.testMethod)); 
     calcthread.Start(input); 
    } 
} 

class CalcClass 
{ 
    public event EventHandler ProgressUpdate; 

    public void testMethod(object input) 
    { 
     //part 1 
     if(ProgressUpdate != null) 
      ProgressUpdate(this, new YourEventArgs(status)); 
     //part 2 
    } 
} 

UPDATE:
Dường như đây vẫn là câu hỏi thường gặp và câu trả lời tôi muốn cập nhật câu trả lời này với cách tôi sẽ làm ngay bây giờ (với .NET 4.5) - đây là lâu hơn một chút như tôi sẽ hiển thị một số khả năng khác nhau:

class MainWindow 
{ 
    Task calcTask = null; 

    void buttonStartCalc_Clicked(object sender, EventArgs e) { StartCalc(); } // #1 
    async void buttonDoCalc_Clicked(object sender, EventArgs e) // #2 
    { 
     await CalcAsync(); // #2 
    } 

    void StartCalc() 
    { 
     var calc = PrepareCalc(); 
     calcTask = Task.Run(() => calc.TestMethod(input)); // #3 
    } 
    Task CalcAsync() 
    { 
     var calc = PrepareCalc(); 
     return Task.Run(() => calc.TestMethod(input)); // #4 
    } 
    CalcClass PrepareCalc() 
    { 
     //your code 
     var calc = new CalcClass(); 
     calc.ProgressUpdate += (s, e) => Dispatcher.Invoke((Action)delegate() 
      { 
       // update UI 
      }); 
     return calc; 
    } 
} 

class CalcClass 
{ 
    public event EventHandler<EventArgs<YourStatus>> ProgressUpdate; // #5 

    public TestMethod(InputValues input) 
    { 
     //part 1 
     ProgressUpdate.Raise(this, status); // #6 - status is of type YourStatus 
     //part 2 
    } 
} 

static class EventExtensions 
{ 
    public static void Raise<T>(this EventHandler<EventArgs<T>> theEvent, 
           object sender, T args) 
    { 
     if (theEvent != null) 
      theEvent(sender, new EventArgs<T>(args)); 
    } 
} 

@ 1) Làm thế nào để bắt đầu tính toán "đồng bộ" và chạy chúng trong nền

@ 2) Làm thế nào để bắt đầu nó "không đồng bộ" và "đang chờ nó": Tại đây việc tính toán được thực hiện và hoàn thành trước khi phương thức trả về, nhưng vì giao diện người dùng async/await không bị chặn (BTW: các trình xử lý sự kiện như vậy là chỉ sử dụng hợp lệ async void làm xử lý sự kiện phải trả lại void - sử dụng async Task trong tất cả các trường hợp khác)

@ 3) Thay vì Thread mới, chúng tôi hiện sử dụng Task. Để sau này có thể kiểm tra hoàn thành (thành công) của chúng tôi, chúng tôi lưu nó vào thành viên calcTask toàn cầu. Trong nền này cũng bắt đầu một chủ đề mới và chạy các hành động ở đó, nhưng nó là dễ dàng hơn nhiều để xử lý và có một số lợi ích khác.

@ 4) Ở đây chúng tôi cũng bắt đầu hành động, nhưng lần này chúng tôi trả lại tác vụ, do đó, "trình xử lý sự kiện không đồng bộ" có thể "đang chờ nó". Chúng tôi cũng có thể tạo async Task CalcAsync() và sau đó await Task.Run(() => calc.TestMethod(input)).ConfigureAwait(false); (FYI: ConfigureAwait(false) là để tránh deadlocks, bạn nên đọc về điều này nếu bạn sử dụng async/await vì nó sẽ là nhiều để giải thích ở đây) mà sẽ dẫn đến cùng một công việc, nhưng các Task.Run là "hoạt động awaitable" duy nhất và là người cuối cùng chúng ta chỉ có thể trả lại nhiệm vụ và lưu một chuyển đổi ngữ cảnh, giúp tiết kiệm thời gian thực hiện.

@ 5) Ở đây bây giờ tôi sử dụng một "sự kiện chung mạnh mẽ gõ" vì vậy chúng tôi có thể vượt qua và nhận "đối tượng tình trạng" của chúng ta dễ dàng

@ 6) Ở đây tôi sử dụng phần mở rộng định nghĩa dưới đây, trong đó (ngoài một cách dễ dàng sử dụng) giải quyết điều kiện chủng tộc có thể có trong ví dụ cũ. Có thể xảy ra sự kiện đó có null sau khi kiểm tra if, nhưng trước cuộc gọi nếu trình xử lý sự kiện đã bị xóa trong một chuỗi khác tại thời điểm đó. Điều này không thể xảy ra ở đây, vì các tiện ích mở rộng nhận được một "bản sao" của đại biểu sự kiện và trong cùng một tình huống, trình xử lý vẫn được đăng ký bên trong phương thức Raise.

+0

Cảm ơn câu trả lời nhanh, Tôi đã thêm mã này vào mã của mình, tuy nhiên trong CalcClass tôi gặp sự cố với "ProgressUpdate", lớp học không nhận ra nó. Có điều gì tôi phải đưa vào sử dụng không? Cho đến nay tôi chỉ cần thêm "using System.Threading". – phil13131

+0

Có, tôi nhận được thông báo lỗi sau: Lỗi Mã thông báo không hợp lệ ';' trong lớp, cấu trúc hoặc khai báo thành viên giao diện – phil13131

+0

Tôi sử dụng mã như đã nêu và nhận lỗi trên dòng nơi "Sự kiện công khai ProgressUpdate"; được khai báo. – phil13131

0

Cảm ơn Chúa, Microsoft đã rằng tìm ra trong WPF :)

Mỗi Control, giống như một thanh tiến trình, nút, hình thức, vv có Dispatcher trên đó. Bạn có thể cung cấp cho Dispatcher một Action mà cần phải được thực hiện và nó sẽ tự động gọi nó trên chủ đề chính xác (một Action giống như một đại biểu chức năng).

Bạn có thể tìm thấy ví dụ here.

Tất nhiên, bạn sẽ phải có quyền truy cập điều khiển từ các lớp khác, ví dụ: bằng cách làm cho nó public và chuyển một tham chiếu đến Window đến lớp khác của bạn hoặc có thể bằng cách chỉ chuyển một tham chiếu đến thanh tiến trình.

+3

Lưu ý rằng khi bạn liên kết đến một ví dụ, thay vì sao chép/dán mã có liên quan ở đây, nếu trang đó bị hỏng (như sáng nay) hoặc xóa nội dung của chúng, câu trả lời này sẽ không có chất cần thiết cho ai đó để làm việc. Tốt hơn để bao gồm nội dung đó. – vapcguy

1

Bạn sẽ phải quay lại chủ đề chính của mình (còn gọi là UI thread) để update giao diện người dùng. Bất kỳ chủ đề nào khác cố gắng cập nhật giao diện người dùng của bạn sẽ chỉ gây ra exceptions để được ném khắp nơi.

Vì vậy, vì bạn đang ở trong WPF, bạn có thể sử dụng Dispatcher và cụ thể hơn là beginInvoke trên số dispatcher này. Điều này sẽ cho phép bạn thực hiện những gì cần thực hiện (thường là Cập nhật giao diện người dùng) trong chuỗi giao diện người dùng.

Bạn cũng muốn "đăng ký" UI trong số business, bằng cách duy trì tham chiếu đến điều khiển/biểu mẫu để bạn có thể sử dụng dispatcher.

+0

Một ví dụ về cú pháp cho người mới đến WPF sẽ hữu ích. – vapcguy

4

Mọi thứ tương tác với giao diện người dùng phải được gọi trong chuỗi giao diện người dùng (trừ khi nó là đối tượng cố định). Để làm điều đó, bạn có thể sử dụng điều phối viên.

var disp = /* Get the UI dispatcher, each WPF object has a dispatcher which you can query*/ 
disp.BeginInvoke(DispatcherPriority.Normal, 
     (Action)(() => /*Do your UI Stuff here*/)); 

Tôi sử dụng BeginInvoke ở đây, thường là người làm việc nền không cần phải chờ cập nhật giao diện người dùng. Nếu bạn muốn chờ, bạn có thể sử dụng Invoke. Nhưng bạn nên cẩn thận không gọi BeginInvoke để nhanh đến thường xuyên, điều này có thể thực sự khó chịu.

Nhân tiện, lớp BackgroundWorker giúp loại taks này. Nó cho phép thay đổi báo cáo, như tỷ lệ phần trăm và tự động gửi điều này từ chuỗi nền vào chuỗi ui. Đối với hầu hết các chủ đề <> cập nhật ui nhiệm vụ BackgroundWorker là một công cụ tuyệt vời.

1

Nếu đây là một phép tính dài thì tôi sẽ chuyển sang chế độ nền. Nó có sự hỗ trợ tiến bộ. Nó cũng có hỗ trợ hủy bỏ.

http://msdn.microsoft.com/en-us/library/cc221403(v=VS.95).aspx 

Ở đây tôi có một TextBox bị ràng buộc vào nội dung.

private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     Debug.Write("backgroundWorker_RunWorkerCompleted"); 
     if (e.Cancelled) 
     { 
      contents = "Cancelled get contents."; 
      NotifyPropertyChanged("Contents"); 
     } 
     else if (e.Error != null) 
     { 
      contents = "An Error Occured in get contents"; 
      NotifyPropertyChanged("Contents"); 
     } 
     else 
     { 
      contents = (string)e.Result; 
      if (contentTabSelectd) NotifyPropertyChanged("Contents"); 
     } 
    } 
+0

Tôi muốn bỏ phiếu này, vì nó đã cho tôi đi đúng hướng và đến một trang MSDN có thông tin tôi cần, nhưng đây là FAR từ một ví dụ đầy đủ. Bạn cần hiển thị * MUCH * nhiều hơn, giống như nơi để khởi tạo công nhân, các thuộc tính và trình xử lý sự kiện để cung cấp cho nó, cho thấy chức năng 'DoWork' đang làm gì và cách báo cáo tiến trình của nó. Điều này chỉ cho thấy phải làm gì khi bạn hủy bỏ nhân viên và hiển thị 'e.Result' có lẽ bạn có một hàm' backgroundWorker_DoWork (đối tượng người gửi, DoWorkEventArgs e) {...} 'cho bạn, nhưng không phải là' DoWork' thực sự là gì làm và cách trả lại kết quả đó là 'Kết quả '. – vapcguy

5

Bạn nói đúng đó bạn nên sử dụng Dispatcher để cập nhật các điều khiển trên thread UI, và cũng đúng rằng các quá trình lâu dài không nên chạy trên thread UI. Ngay cả khi bạn chạy quy trình chạy lâu không đồng bộ trên chuỗi giao diện người dùng, nó vẫn có thể gây ra vấn đề về hiệu suất.

Cần lưu ý rằng Dispatcher.CurrentDispatcher sẽ trả về điều phối cho chuỗi hiện tại, không nhất thiết phải là chuỗi giao diện người dùng. Tôi nghĩ bạn có thể sử dụng Application.Current.Dispatcher để nhận tham chiếu đến điều phối của chuỗi giao diện người dùng nếu có sẵn cho bạn, nhưng nếu không bạn sẽ phải chuyển bộ điều phối giao diện người dùng vào chuỗi nền của mình.

Thông thường tôi sử dụng Task Parallel Library cho hoạt động luồng thay vì BackgroundWorker. Tôi chỉ thấy dễ sử dụng hơn.

Ví dụ,

Task.Factory.StartNew(() => 
    SomeObject.RunLongProcess(someDataObject)); 

nơi

void RunLongProcess(SomeViewModel someDataObject) 
{ 
    for (int i = 0; i <= 1000; i++) 
    { 
     Thread.Sleep(10); 

     // Update every 10 executions 
     if (i % 10 == 0) 
     { 
      // Send message to UI thread 
      Application.Current.Dispatcher.BeginInvoke(
       DispatcherPriority.Normal, 
       (Action)(() => someDataObject.ProgressValue = (i/1000))); 
     } 
    } 
} 
28

Tôi sẽ ném bạn một quả bóng đường cong ở đây. Nếu tôi đã nói điều đó một lần tôi đã nói nó một trăm lần. Các hoạt động Marshaling như Invoke hoặc BeginInvoke không phải lúc nào cũng là phương pháp tốt nhất để cập nhật giao diện người dùng với tiến trình luồng công nhân.

Trong trường hợp này, nó thường hoạt động tốt hơn để chuỗi công nhân xuất bản thông tin tiến trình của nó lên cấu trúc dữ liệu được chia sẻ mà chuỗi giao diện người dùng sau đó thăm dò ý kiến ​​theo khoảng thời gian đều đặn. Điều này có một số lợi thế.

  • Nó phá vỡ khớp nối chặt chẽ giữa giao diện người dùng và chuỗi công nhân mà Invoke áp đặt.
  • Chuỗi giao diện người dùng sẽ ra lệnh khi các điều khiển giao diện người dùng được cập nhật ... cách bạn nên thực hiện khi bạn thực sự nghĩ về nó.
  • Không có nguy cơ tràn ngập hàng đợi thông điệp UI như trường hợp nếu BeginInvoke được sử dụng từ chuỗi công nhân.
  • Chuỗi công nhân không phải chờ phản hồi từ chuỗi giao diện người dùng như trường hợp với Invoke.
  • Bạn nhận được nhiều thông lượng hơn cho cả chuỗi giao diện người dùng và chuỗi công việc.
  • InvokeBeginInvoke là các hoạt động tốn kém.

Vì vậy, trong calcClass hãy tạo cấu trúc dữ liệu sẽ giữ thông tin tiến trình.

public class calcClass 
{ 
    private double percentComplete = 0; 

    public double PercentComplete 
    { 
    get 
    { 
     // Do a thread-safe read here. 
     return Interlocked.CompareExchange(ref percentComplete, 0, 0); 
    } 
    } 

    public testMethod(object input) 
    { 
    int count = 1000; 
    for (int i = 0; i < count; i++) 
    { 
     Thread.Sleep(10); 
     double newvalue = ((double)i + 1)/(double)count; 
     Interlocked.Exchange(ref percentComplete, newvalue); 
    } 
    } 
} 

Sau đó, trong MainWindow sử dụng lớp học của bạn một DispatcherTimer định kỳ thăm dò ý kiến ​​các thông tin tiến bộ. Định cấu hình DispatcherTimer để tăng sự kiện Tick vào bất kỳ khoảng thời gian nào phù hợp nhất cho trường hợp của bạn.

public partial class MainWindow : Window 
{ 
    public void YourDispatcherTimer_Tick(object sender, EventArgs args) 
    { 
    YourProgressBar.Value = calculation.PercentComplete; 
    } 
} 
+1

Xin chào Brian, tôi nghĩ câu trả lời của bạn rất đơn giản và thanh lịch. Tôi đã tự hỏi trong ý kiến ​​của bạn, làm thế nào nhanh chóng là dispatchertimer đánh dấu bắn? Theo kinh nghiệm của bạn, tỷ lệ cập nhật chấp nhận được là bao nhiêu? Cảm ơn. – DoubleDunk

+2

@DoubleDunk: Có thể là từ 500ms đến 2000ms. Bất kỳ nhanh hơn và người dùng sẽ không thể cho biết sự khác biệt. Bất kỳ chậm hơn và người dùng sẽ tự hỏi tại sao thanh tiến trình không tăng lên. Quyết định một số dư tốt hoạt động tốt nhất cho ứng dụng và người dùng của bạn. –

+0

@BrianGideon Tỷ lệ cập nhật sẽ phụ thuộc vào thời gian tính toán sẽ mất bao lâu. Nếu nó được cập nhật hai lần một giây và toàn bộ tính toán chỉ mất khoảng 3 giây, thì thanh tiến trình sẽ vẫn có các bước nhảy lớn. Nếu calcualtions mặt khác mất 20 phút (như họ đã cho tôi trong một trường hợp) sau đó 500ms là do quá thường xuyên. Câu trả lời hay và một giải pháp thay thế tốt để Gọi! – phil13131

0

Cảm thấy cần phải thêm câu trả lời này tốt hơn, vì không có gì ngoại trừ BackgroundWorker dường như giúp tôi và câu trả lời liên quan đến điều đó đến nay vẫn chưa hoàn chỉnh. Đây là cách bạn sẽ cập nhật một trang XAML gọi MainWindow có thẻ hình ảnh như thế này:

<Image Name="imgNtwkInd" Source="Images/network_on.jpg" Width="50" /> 

với một quá trình BackgroundWorker để hiển thị nếu bạn đang kết nối với mạng hay không:

using System.ComponentModel; 
using System.Windows; 
using System.Windows.Controls; 

public partial class MainWindow : Window 
{ 
    private BackgroundWorker bw = new BackgroundWorker(); 

    public MainWindow() 
    { 
     InitializeComponent(); 

     // Set up background worker to allow progress reporting and cancellation 
     bw.WorkerReportsProgress = true; 
     bw.WorkerSupportsCancellation = true; 

     // This is your main work process that records progress 
     bw.DoWork += new DoWorkEventHandler(SomeClass.DoWork); 

     // This will update your page based on that progress 
     bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged); 

     // This starts your background worker and "DoWork()" 
     bw.RunWorkerAsync(); 

     // When this page closes, this will run and cancel your background worker 
     this.Closing += new CancelEventHandler(Page_Unload); 
    } 

    private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     BitmapImage bImg = new BitmapImage(); 
     bool connected = false; 
     string response = e.ProgressPercentage.ToString(); // will either be 1 or 0 for true/false -- this is the result recorded in DoWork() 

     if (response == "1") 
      connected = true; 

     // Do something with the result we got 
     if (!connected) 
     { 
      bImg.BeginInit(); 
      bImg.UriSource = new Uri("Images/network_off.jpg", UriKind.Relative); 
      bImg.EndInit(); 
      imgNtwkInd.Source = bImg; 
     } 
     else 
     { 
      bImg.BeginInit(); 
      bImg.UriSource = new Uri("Images/network_on.jpg", UriKind.Relative); 
      bImg.EndInit(); 
      imgNtwkInd.Source = bImg; 
     } 
    } 

    private void Page_Unload(object sender, CancelEventArgs e) 
    { 
     bw.CancelAsync(); // stops the background worker when unloading the page 
    } 
} 


public class SomeClass 
{ 
    public static bool connected = false; 

    public void DoWork(object sender, DoWorkEventArgs e) 
    { 
     BackgroundWorker bw = sender as BackgroundWorker; 

     int i = 0; 
     do 
     { 
      connected = CheckConn(); // do some task and get the result 

      if (bw.CancellationPending == true) 
      { 
       e.Cancel = true; 
       break; 
      } 
      else 
      { 
       Thread.Sleep(1000); 
       // Record your result here 
       if (connected) 
        bw.ReportProgress(1); 
       else 
        bw.ReportProgress(0); 
      } 
     } 
     while (i == 0); 
    } 

    private static bool CheckConn() 
    { 
     bool conn = false; 
     Ping png = new Ping(); 
     string host = "SomeComputerNameHere"; 

     try 
     { 
      PingReply pngReply = png.Send(host); 
      if (pngReply.Status == IPStatus.Success) 
       conn = true; 
     } 
     catch (PingException ex) 
     { 
      // write exception to log 
     } 
     return conn; 
    } 
} 

Đối thêm thông tin: https://msdn.microsoft.com/en-us/library/cc221403(v=VS.95).aspx

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