2013-04-25 29 views
7

Tôi đọc rất nhiều sự kiện và chủ đề thảo luận, nhưng tất cả sau đó tập trung vào "những gì xảy ra" nếu tôi unsuscribe từ một sự kiện và cố gắng gọi nó sau này. Câu hỏi của tôi khác ... điều gì sẽ xảy ra nếu tôi có quy trình trong chuỗi A kích hoạt sự kiện "Tôi kết thúc" trong mili giây 1 và cũng có quy trình trong chuỗi B kích hoạt sự kiện "Tôi hoàn thành" sau mili giây 2thực hiện sự kiện C# là luồng an toàn?

Cả hai quy trình đều được gán cho cùng một phương pháp để nghe và xử lý sự kiện. Vì vậy, C# phải thực thi phương thức xử lý sự kiện 2 lần: 1 lần cho sự kiện được kích hoạt trong chuỗi A và 1 lần cho sự kiện được kích hoạt từ chủ đề B.

Điều gì sẽ xảy ra? Liệu C# có khóa phương thức này khi "sự kiện đầu tiên đến từ luồng A" bắt đầu thực hiện phương thức xử lý sự kiện và mở khóa phương thức khi nó kết thúc thực thi, do đó cho phép các sự kiện "chờ đợi" khác thực thi nội dung phương thức?

Hoặc sự kiện được kích hoạt từ luồng A sẽ bắt đầu thực hiện phương thức xử lý sự kiện và 1 mili giây sau sự kiện được kích hoạt từ chuỗi B cũng sẽ bắt đầu thực hiện trên cùng một phương thức thông báo hiện tại phương pháp đang được được thực hiện bởi "quá trình" khác? Tôi yêu cầu điều này, vì tôi muốn viết một số tập tin trong phương thức bắt sự kiện, nhưng nếu phương pháp này có thể được thực thi đồng thời (tùy thuộc vào thời điểm sự kiện được kích hoạt), tôi đoán tôi không thể làm điều đó ở đây, vì thông tin trên tệp sẽ là kết hợp giữa 2 quy trình ghi vào cùng một tệp tại cùng một thời điểm (không phải thông tin hợp lệ trên tệp).

Mã của tôi trông như thế này (hơi dài một chút, xin lỗi). xin lưu ý điều này sẽ không biên dịch, chỉ là một mẫu để hiển thị những gì Im làm:

public partial class MainForm : Form 
{ 
    FTPClientManager client = null; 

    public MainForm() 
    { 
     InitializeComponent(); 
    } 

    private void btnConnect_Click(object sender, EventArgs e) 
    { 
     Connect(this.tbFTPServer.Text.Trim()); 
     this.lstLog.Items.Add("Connected"); //This lstLog is a list box that will have a list of downloaded files from all threads. 
    } 

    void Connect(string urlStr) 
    { 
     try { 
      client = new FTPClientManager(); 
      //subscribe to the event 
      client.FileDownloadCompleted += new EventHandler<FileDownloadCompletedEventArgs>(client_FileDownloadCompleted); 
     } 
     catch (Exception ex) 
     { 
      MessageBox.Show(ex.Message); 
     } 
    } 

    void client_FileDownloadCompleted(object sender, FileDownloadCompletedEventArgs e) 
    { 
     this.Invoke(new EventHandler<FileDownloadCompletedEventArgs>(
      client_FileDownloadCompletedHandler), sender, e); 
    } 

    void client_FileDownloadCompletedHandler(object sender, FileDownloadCompletedEventArgs e) 
    { 
     string log = string.Format("{0} Instance {5} Download from {1} to {2} is completed. Length: {3} Time: {4}. ", 
      DateTime.Now, e.ServerPath, e.LocalFile.FullName, e.LocalFile.Length, e.DownloadTime, e.ftpInstance); 

     this.lstLog.Items.Add(log); 
    } 

    private void btnDownload_Click(object sender, EventArgs e) 
    { 
     client.DownloadFiles(); 
    } 
} 

public class FTPClientManager { 
    FTPDownloadClient[] arrayDownloadClient = new FTPDownloadClient[2];   
    public event EventHandler<FileDownloadCompletedEventArgs> FileDownloadCompleted; 

    public void DownloadFiles() 
    { 
     for (int i = 0; i < 2; i++) 
     { 
      arrayDownloadClient[i] = new FTPDownloadClient(); 
      //subscribe to the event. each instance of FTPDownloadClient will suscribe to the same event. 
      arrayDownloadClient[i].FileDownloadCompleted += new EventHandler<FileDownloadCompletedEventArgs>(downloadClient_FileDownloadCompleted); 
     } 

     //download one set of files in thread A 
     arrayDownloadClient[0].DownloadFiles(list_of_files_to_download); 

     //download another set of files in thread B 
     arrayDownloadClient[1].DownloadFiles(another_list_of_files_to_download); 
    } 

    //In theory, the method downloadClient_FileDownloadCompleted will be executed by any instance of FTPDownloadClient 
    //running in either thread A or thread B, whichever finish first downloading a file. 
    //My question comes in the execution of this method. 
    //Lets say the process in thread A finish downloading and fires the event. 
    //Lets say the process in thread B finish downloading 1 millisecond after thread A finish, so it also fires the event. 
    //how C# manage the execution of the downloadClient_FileDownloadCompleted?? 
    //does the event coming from thread A will lock the method downloadClient_FileDownloadCompleted, execute it, and when finish execution unlock the method 
    //and allows the event coming from thread B start locking, processing, unlock ?? 
    //Or the method will be executed "at the same time" (1 millisecond difference) by each event fired from thread A and thread B?? 
    void downloadClient_FileDownloadCompleted(object sender, FileDownloadCompletedEventArgs e) 
    { 
     this.OnFileDownloadCompleted(e); 
    } 

    protected virtual void OnFileDownloadCompleted(FileDownloadCompletedEventArgs e) 
    { 
     if (FileDownloadCompleted != null) 
     { 
      //this will fire the event, so the main form will catch it 
      //again, this fire can be triggered from process in thread A or from process in thread B 
      FileDownloadCompleted(this, e); 
     } 
    } 
} 

public class FTPDownloadClient { 
    public event EventHandler<FileDownloadCompletedEventArgs> 
      FileDownloadCompleted; 

    public void DownloadFiles(string [] files_to_download) 
    { 
     ParameterizedThreadStart threadStart = 
       new ParameterizedThreadStart(StartDownloadFiles); 
      Thread downloadThread = new Thread(threadStart); 
      downloadThread.IsBackground = true; 
      downloadThread.Start(new object[] { files_to_donwload }); 
    } 

    //This metod will download all the files in the list passed as parameter. 
    //Every file downloaded will raise the event FileDownloadComplete, so a message can be added to the lstlog on the main form 
    void StartDownloadFiles(object state) 
     { 
      var paras = state as object[]; 

      string [] files = paras[0] as string []; 

      foreach (var file in files) 
      { 
       DownloadOneFile(file); 
      } 
     } 

    void DownloadFile(string onefile) 
    { 
      //Donwload file done here 
      var fileDownloadCompletedEventArgs = new FileDownloadCompletedEventArgs 
      { 
       LocalFile = new FileInfo(destPath), 
       ServerPath = onefile, 
       DownloadTime = fileDownloadTime.ElapsedMilliseconds.ToString() 
      }; 

      this.OnFileDownloadCompleted(fileDownloadCompletedEventArgs); 
    } 

    protected virtual void OnFileDownloadCompleted(FileDownloadCompletedEventArgs e) 
     { 
      if (FileDownloadCompleted != null) 
      { 
       //the event is fired when the file being downloaded by this thread is finish. 
       //so, thread A will fire this event from its current thread 
       //and also thread B will fire the same event from its own thread. 
       FileDownloadCompleted(this, e); 
      } 
     } 
} 
+1

http://stackoverflow.com/questions/3480036/multiple-threads-subscribing-same-event – nicholas

Trả lời

12

C# sẽ không làm bất kỳ khóa cho bạn. Nếu các sự kiện có thể được nâng lên bởi nhiều luồng cùng lúc, bạn phải viết mã để xử lý (nếu cần).

Bạn có thể sử dụng một lock statement để ngăn chặn nhiều chủ đề từ thực hiện nó:

private void MyEventHandler(object sender, EventArgs e) 
{ 
    lock (lockingObject) 
    { 
     // Handle event here. 
     // Only one thread at a time can reach this code. 
    } 
} 

đâu lockingObject là một lĩnh vực bên trong lớp học của bạn tuyên bố như:

private readonly object lockingObject = new object(); 

Bạn cũng phải cẩn thận về luồng theo phương thức tăng sự kiện.

Giả sử bạn có một sự kiện trong lớp học của mình được gọi là MyEvent. Bạn nên không làm điều này:

private void RaiseMyEvent() 
{ 
    if (MyEvent != null)     // {1} 
     MyEvent(this, new EventArgs()); // {2} 
} 

Nếu thread khác có thể tách từ MyEvent, sau đó nó có thể là nó có thể tách giữa dòng {1} và dòng {2}. Nếu điều đó xảy ra, dòng {2} sẽ ném một ngoại lệ tham chiếu null vì MyEvent sẽ đột nhiên trở thành null!

Các cách chính xác để làm điều này là:

private void RaiseMyEvent() 
{ 
    var handler = MyEvent; 

    if (handler != null) 
     handler (this, new EventArgs()); 
} 

Bây giờ ngoại trừ tham chiếu null không thể xảy ra.

Tuy nhiên, lưu ý rằng khi sử dụng nhiều chuỗi, trình xử lý sự kiện có thể được gọi là sau chủ đề đã tách nó ra!

+0

+1 để quản lý tình trạng cuộc đua "chưa đăng ký sau khi kiểm tra không xác định" – tobsen

+2

Với C# 6 mới '? .' điều hành nó cũng có thể làm 'MyEvent? .Invoke (this, new EventArgs())' [MSDN] (https://msdn.microsoft.com/en-us/library/dn986595.aspx#code-snippet-4) – SWdV

1

Có vẻ như bạn muốn sử dụng khóa. Nó ngăn chặn các chủ đề khác nhau thực hiện cùng một mã tại thời điểm mẫu. Tuy nhiên, không nên sử dụng khóa và nên tránh hầu hết thời gian, nhưng có vẻ ổn trong trường hợp của bạn.

Lock statement

Tôi thích ví dụ Microsoft cung cấp cho bạn với rút tiền

int Withdraw(int amount) 
    { 

     // This condition never is true unless the lock statement 
     // is commented out. 
     if (balance < 0) 
     { 
      throw new Exception("Negative Balance"); 
     } 

     // Comment out the next line to see the effect of leaving out 
     // the lock keyword. 
     lock (thisLock) 
     { 
      if (balance >= amount) 
      { 
       Console.WriteLine("Balance before Withdrawal : " + balance); 
       Console.WriteLine("Amount to Withdraw  : -" + amount); 
       balance = balance - amount; 
       Console.WriteLine("Balance after Withdrawal : " + balance); 
       return amount; 
      } 
      else 
      { 
       return 0; // transaction rejected 
      } 
     } 
    } 
+0

Không bao giờ 'khóa (này)' hoặc khóa trên Loại. Thay vào đó, hãy tạo một "syncObject" mới như [được giải thích tại đây] (http://msdn.microsoft.com/en-us/library/c5kehkcz (v = vs.80) .aspx) (một phiên bản mới hơn của bài viết msdn mà bạn kêt nôi đên). – tobsen

+0

Đã không nhận ra nó không phải là phiên bản cuối cùng của arcticle. Đồng ý, cập nhật nó. Không bao giờ là một chút khắc nghiệt, nhưng tôi đồng ý nó luôn luôn tốt hơn để sử dụng xây dựng này. –

0

Dường như bạn đã thực hiện chủ đề mã của bạn an toàn vì bạn đang sử dụng Form.Invoke trong phương pháp của bạn client_FileDownloadCompleted.

Đúng là sự kiện FTPClientManager.FileDownloadCompleted có thể kích hoạt đồng thời nhiều chủ đề khác nhau, nhưng Form.Invoke sẽ tuần tự hóa mọi cuộc gọi trở lại luồng giao diện người dùng chính của bạn. Do đó, trong mã của bạn, bạn không cần bất kỳ khóa nào là Form.Invoke sẽ mất và client_FileDownloadCompletedHandler sẽ luôn được gọi trên chuỗi giao diện người dùng của bạn.

+0

cảm ơn câu trả lời. Tuy nhiên, tôi sẽ cần phải sử dụng khóa, kể từ khi Im lập kế hoạch để viết vào một tập tin. – user2232787

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