2015-04-13 17 views
8

Tôi có một ứng dụng biểu mẫu cửa sổ, trong đó tôi gửi email bằng SmtpClient. Các hoạt động không đồng bộ khác trong ứng dụng sử dụng async/await và tôi lý tưởng muốn nhất quán trong khi gửi thư.Làm thế nào nên chờ đợi một nhiệm vụ không đồng bộ và hiển thị một hình thức phương thức trong cùng một phương pháp được xử lý?

Tôi hiển thị hộp thoại phương thức có nút hủy khi gửi thư và kết hợp SendMailAsync với biểu mẫu.ShowDialog là nơi mọi thứ trở nên phức tạp bởi vì việc chờ gửi sẽ chặn và ShowDialog sẽ như vậy. Cách tiếp cận hiện tại của tôi là như dưới đây, nhưng có vẻ lộn xộn, có cách tiếp cận tốt hơn cho điều này không?

private async Task SendTestEmail() 
{ 
    // Prepare message, client, and form with cancel button 
    using (Message message = ...) 
    { 
    SmtpClient client = ... 
    CancelSendForm form = ... 

    // Have the form button cancel async sends and 
    // the client completion close the form 
    form.CancelBtn.Click += (s, a) => 
    { 
     client.SendAsyncCancel(); 
    }; 
    client.SendCompleted += (o, e) => 
    { 
     form.Close(); 
    }; 

    // Try to send the mail 
    try 
    { 
     Task task = client.SendMailAsync(message); 
     form.ShowDialog(); 
     await task; // Probably redundant 

     MessageBox.Show("Test mail sent", "Success"); 
    } 
    catch (Exception ex) 
    { 
     string text = string.Format(
      "Error sending test mail:\n{0}", 
      ex.Message); 
     MessageBox.Show(text, "Error"); 
    } 
    } 
+0

này dường như là một giải pháp thực sự gọn gàng. Bạn chính xác rằng 'await' là không cần thiết nhưng nó là tốt đẹp và dễ đọc. – Gusdor

+0

Cảm ơn, tôi đã thử giải pháp bạn đã thêm vào rồi xóa. Nhưng form.Show (điều này) (nơi đây là hình thức chính) không còn xử lý các hình thức chính như cha mẹ vì vậy tôi bị mất hành vi từ form.StartPosition = FormStartPosition.CenterScreen mà tôi đã không bao gồm ở trên. Cố gắng để sau đó thiết lập các phụ huynh để các hình thức hiện tại ném một ngoại lệ về hình thức được cấp cao nhất, và form.TopLevel là chỉ đọc (thỏ lỗ! :)) – FlintZA

+0

Tôi loại bỏ câu trả lời vì tôi đã sai :) – Gusdor

Trả lời

10

Tôi sẽ xem xét xử lý sự kiện Form.Shown và gửi email từ đó. Vì nó sẽ kích hoạt không đồng bộ, bạn không cần phải lo lắng về tính chất chặn "làm việc xung quanh" của ShowDialog và bạn có cách hơi sạch hơn để đồng bộ hóa việc đóng biểu mẫu và hiển thị thông báo thành công hoặc thất bại.

form.Shown += async (s, a) => 
{ 
    try 
    { 
     await client.SendMailAsync(message); 
     form.Close(); 
     MessageBox.Show("Test mail sent", "Success"); 
    } 
    catch(Exception ex) 
    { 
     form.Close(); 
     string text = string.Format(
      "Error sending test mail:\n{0}", 
      ex.Message); 
     MessageBox.Show(text, "Error"); 
    } 
}; 

form.ShowDialog(); 
+1

Điều này không thực sự làm nhiều để thêm vào khả năng đọc. Những gì nó làm là sửa chữa lỗi điều kiện chủng tộc trong mã ban đầu, trong đó nếu hoạt động hoàn thành trước khi hộp thoại được hiển thị (một rủi ro nhỏ được thừa nhận, nhưng một lỗi không được thực thi một cách hợp lý), 'Close() 'phương thức sẽ được gọi vào sai thời gian (tức là trước khi hộp thoại được hiển thị). Xử lý 'try' /' catch' có thể được chuyển đến trình xử lý sự kiện 'Shown' để nắm bắt các ngoại lệ được ném bởi' await'. –

+0

Đồng ý với tất cả các điểm. Cập nhật mã của tôi - try/catch xuất hiện để xử lý các lỗi gửi email và chắc chắn thuộc về trình xử lý sự kiện. –

+0

Cảm ơn, tôi nghĩ rằng bạn đang đúng đây là một cách tiếp cận tốt hơn cho vấn đề cụ thể trong danh sách ban đầu. Tôi cũng thích rằng nó xử lý các cơ hội nhỏ của vấn đề cuộc gọi không thành công như @Peter Duniho đã đề cập. Sau khi chỉnh sửa của bạn tôi cũng nghĩ rằng nó ít nhất là có thể đọc được như bản gốc nếu không nhiều hơn như vậy. – FlintZA

1

Một điều đáng ngờ về SendTestEmail thực hiện hiện tại của bạn là nó trong thực tế đồng bộ, mặc dù nó trả về một Task. Vì vậy, nó chỉ trả về khi tác vụ đã hoàn thành, bởi vì ShowDialog là đồng bộ (một cách tự nhiên, vì hộp thoại là phương thức).

Điều này có thể gây nhầm lẫn. Ví dụ, đoạn mã sau sẽ không làm việc theo cách mong đợi:

var sw = new Stopwatch(); 
sw.Start(); 
var task = SendTestEmail(); 
while (!task.IsCompleted) 
{ 
    await WhenAny(Task.Delay(500), task); 
    StatusBar.Text = "Lapse, ms: " + sw.ElapsedMilliseconds; 
} 
await task; 

Nó có thể dễ dàng giải quyết bằng Task.Yield, mà sẽ cho phép tiếp tục không đồng bộ trên mới (lồng) modal vòng lặp thông điệp thoại:

public static class FormExt 
{ 
    public static async Task<DialogResult> ShowDialogAsync(
     Form @this, CancellationToken token = default(CancellationToken)) 
    { 
     await Task.Yield(); 
     using (token.Register(() => @this.Close(), useSynchronizationContext: true)) 
     { 
      return @this.ShowDialog(); 
     } 
    } 
} 

Sau đó, bạn có thể làm một cái gì đó như thế này (chưa được kiểm tra):

private async Task SendTestEmail(CancellationToken token) 
{ 
    // Prepare message, client, and form with cancel button 
    using (Message message = ...) 
    { 
     SmtpClient client = ... 
     CancelSendForm form = ... 

     // Try to send the mail 
     var ctsDialog = CancellationTokenSource.CreateLinkedTokenSource(token); 
     var ctsSend = CancellationTokenSource.CreateLinkedTokenSource(token); 
     var dialogTask = form.ShowDialogAsync(ctsDialog.Token); 
     var emailTask = client.SendMailExAsync(message, ctsSend.Token); 
     var whichTask = await Task.WhenAny(emailTask, dialogTask); 
     if (whichTask == emailTask) 
     { 
      ctsDialog.Cancel(); 
     } 
     else 
     { 
      ctsSend.Cancel(); 
     } 
     await Task.WhenAll(emailTask, dialogTask); 
    } 
} 

public static class SmtpClientEx 
{ 
    public static async Task SendMailExAsync(
     SmtpClient @this, MailMessage message, 
     CancellationToken token = default(CancellationToken)) 
    { 
     using (token.Register(() => 
      @this.SendAsyncCancel(), useSynchronizationContext: false)) 
     { 
      await @this.SendMailAsync(message); 
     } 
    } 
} 
+0

@ScottChamberlain, cảm ơn vì đã chỉ ra 'SendAsyncCancel' vẫn có thể sử dụng được với' SendMailAsync', mặc dù không có giấy tờ. Đoạn mã trên dựa vào đó. Mặc dù, tôi có thể gắn bó với một triển khai tùy chỉnh như [this one] (http://stackoverflow.com/a/28445791/1768303). – Noseratio

+1

Cảm ơn, tôi thực sự đã học được rất nhiều điều mới về mã async/await từ câu trả lời này. Tôi đã chọn để chọn câu trả lời của @Todd Menier bởi vì tôi nghĩ rằng anh ấy đúng rằng tôi chỉ nên di chuyển thư gửi mã vào trình xử lý Hiển thị của biểu mẫu. Tôi nghĩ rằng bạn là một câu trả lời tốt hơn cho tiêu đề (tổng quát hơn) của câu hỏi ban đầu, vì vậy tôi đã cập nhật tiêu đề để giới hạn nó cho vấn đề biểu mẫu. Ngoài ra, cảm ơn cho lưu ý về điều này không thực sự được async, bạn đúng và sử dụng câu trả lời của Todd tôi có lẽ sẽ chỉ cần loại bỏ các sửa đổi không đồng bộ. – FlintZA

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