2013-09-03 42 views
17

Tôi cần phải viết một số mã không đồng bộ về cơ bản cố gắng liên tục nói chuyện và khởi tạo cơ sở dữ liệu. Khá thường xuyên nỗ lực đầu tiên sẽ thất bại do đó yêu cầu cho nó để thử lại.Tốt nhất không đồng bộ trong khi phương pháp

Trong ngày xưa tôi đã sử dụng một mô hình tương tự như:

void WaitForItToWork() 
{ 
    bool succeeded = false; 
    while (!succeeded) 
    { 
     // do work 
     succeeded = outcome; // if it worked, mark as succeeded, else retry 
     Threading.Thread.Sleep(1000); // arbitrary sleep 
    } 
} 

tôi nhận ra rất nhiều thay đổi đã được thực hiện gần đây cho NET liên quan đến mô hình async vì vậy câu hỏi của tôi thực sự là này tốt nhất phương pháp để sử dụng hoặc là nó có giá trị trong khi khám phá các công cụ async và nếu như vậy làm thế nào để thực hiện mô hình này trong async?

Cập nhật

Chỉ cần làm rõ, tôi muốn đẻ trứng công việc này không đồng bộ để các phương pháp mà spawns nó không phải đợi cho nó để kết thúc vì nó sẽ được sinh ra trong các nhà xây dựng của một dịch vụ để hàm tạo phải trả về ngay lập tức.

Trả lời

25

Bạn có thể cấu trúc lại đoạn đó như thế này:

async Task<bool> WaitForItToWork() 
{ 
    bool succeeded = false; 
    while (!succeeded) 
    { 
     // do work 
     succeeded = outcome; // if it worked, make as succeeded, else retry 
     await Task.Delay(1000); // arbitrary delay 
    } 
    return succeeded; 
} 

Rõ ràng, lợi ích duy nhất nó sẽ cung cấp cho bạn là sử dụng hiệu quả hơn các hồ bơi thread, bởi vì nó không phải lúc nào mất cả một sợi để làm cho sự chậm trễ xảy ra.

Tùy thuộc vào cách bạn có được outcome, có thể có nhiều cách hiệu quả hơn để hoàn thành công việc này bằng cách sử dụng async/await. Thường thì bạn có thể có một cái gì đó như GetOutcomeAsync() mà sẽ làm cho một dịch vụ web, cơ sở dữ liệu hoặc cuộc gọi socket không đồng bộ một cách tự nhiên, vì vậy bạn chỉ cần làm var outcome = await GetOutcomeAsync().

Điều quan trọng là phải xem xét rằng WaitForItToWork sẽ được chia thành các phần theo trình biên dịch và một phần từ dòng await sẽ được tiếp tục không đồng bộ. Here's có lẽ là giải thích tốt nhất về cách thực hiện nội bộ. Vấn đề là, thường là tại một số điểm của mã của bạn, bạn cần phải đồng bộ hóa trên kết quả của nhiệm vụ không đồng bộ. Ví dụ .:

private void Form1_Load(object sender, EventArgs e) 
{ 
    Task<bool> task = WaitForItToWork(); 
    task.ContinueWith(_ => { 
     MessageBox.Show("WaitForItToWork done:" + task.Result.toString()); // true or false 
    }, TaskScheduler.FromCurrentSynchronizationContext()); 
} 

Bạn có thể chỉ đơn giản là thực hiện điều này:

private async void Form1_Load(object sender, EventArgs e) 
{ 
    bool result = await WaitForItToWork(); 
    MessageBox.Show("WaitForItToWork done:" + result.toString()); // true or false 
} 

Đó tuy nhiên sẽ làm cho Form1_Load một phương pháp async quá.

[UPDATE]

Dưới đây là nỗ lực của tôi để minh họa những gì async/await thực sự thực hiện trong trường hợp này. Tôi đã tạo hai phiên bản có cùng một logic, WaitForItToWorkAsync (sử dụng async/await) và WaitForItToWorkAsyncTap (sử dụng TAP pattern mà không cần async/await). Phiên bản frist khá tầm thường, không giống phiên bản thứ hai. Vì vậy, trong khi async/await phần lớn là đường cú pháp của trình biên dịch, nó làm cho mã không đồng bộ dễ dàng hơn nhiều để viết và hiểu.

// fake outcome() method for testing 
bool outcome() { return new Random().Next(0, 99) > 50; } 

// with async/await 
async Task<bool> WaitForItToWorkAsync() 
{ 
    var succeeded = false; 
    while (!succeeded) 
    { 
     succeeded = outcome(); // if it worked, make as succeeded, else retry 
     await Task.Delay(1000); 
    } 
    return succeeded; 
} 

// without async/await 
Task<bool> WaitForItToWorkAsyncTap() 
{ 
    var context = TaskScheduler.FromCurrentSynchronizationContext(); 
    var tcs = new TaskCompletionSource<bool>(); 
    var succeeded = false; 
    Action closure = null; 

    closure = delegate 
    { 
     succeeded = outcome(); // if it worked, make as succeeded, else retry 
     Task.Delay(1000).ContinueWith(delegate 
     { 
      if (succeeded) 
       tcs.SetResult(succeeded); 
      else 
       closure(); 
     }, context); 
    }; 

    // start the task logic synchronously 
    // it could end synchronously too! (e.g, if we used 'Task.Delay(0)') 
    closure(); 

    return tcs.Task; 
} 

// start both tasks and handle the completion of each asynchronously 
private void StartWaitForItToWork() 
{ 
    WaitForItToWorkAsync().ContinueWith((t) => 
    { 
     MessageBox.Show("WaitForItToWorkAsync complete: " + t.Result.ToString()); 
    }, TaskScheduler.FromCurrentSynchronizationContext()); 

    WaitForItToWorkAsyncTap().ContinueWith((t) => 
    { 
     MessageBox.Show("WaitForItToWorkAsyncTap complete: " + t.Result.ToString()); 
    }, TaskScheduler.FromCurrentSynchronizationContext()); 
} 

// await for each tasks (StartWaitForItToWorkAsync itself is async) 
private async Task StartWaitForItToWorkAsync() 
{ 
    bool result = await WaitForItToWorkAsync(); 
    MessageBox.Show("WaitForItToWorkAsync complete: " + result.ToString()); 

    result = await WaitForItToWorkAsyncTap(); 
    MessageBox.Show("WaitForItToWorkAsyncTap complete: " + result.ToString()); 
} 

Một vài từ trên luồng. Không có chủ đề bổ sung được tạo rõ ràng ở đây.Bên trong, thực hiện Task.Delay() có thể sử dụng chủ đề pool (tôi nghi ngờ họ sử dụng Timer Queues), nhưng trong ví dụ cụ thể này (một ứng dụng WinForms), việc tiếp tục sau await sẽ xảy ra trên cùng một chuỗi giao diện người dùng. Trong các môi trường thực thi khác (ví dụ: ứng dụng bảng điều khiển), nó có thể tiếp tục trên một chuỗi khác. IMO, this article bởi Stephen Cleary là phải đọc để hiểu khái niệm luồng async/await.

+0

Tôi có thể gọi điều này đơn giản bằng cách thực hiện 'this.WaitForItToWork();' - thư viện async sẽ xử lý luồng cho tôi? – Chris

+1

Bạn sẽ gọi nó như 'await this.WaitForItToWork()', và toàn bộ chuỗi các cuộc gọi phải được cấu trúc lại để hỗ trợ điều này ... Tôi sẽ giải thích về câu trả lời của tôi để bao gồm thêm thông tin này. – Noseratio

+0

@Chris: bạn phải nhớ sử dụng từ khóa "đang chờ". Quy tắc của ngón tay cái: Luôn luôn "chờ đợi" phải được kết hợp với chức năng "không đồng bộ". Vì vậy, bạn nên làm một cái gì đó như chờ đợi WaitForItToWork(); –

0

Nếu nhiệm vụ là không đồng bộ bạn có thể thử với:

async Task WaitForItToWork() 
    { 
     await Task.Run(() => 
     { 
      bool succeeded = false; 
      while (!succeeded) 
      { 
       // do work 
       succeeded = outcome; // if it worked, make as succeeded, else retry 
       System.Threading.Thread.Sleep(1000); // arbitrary sleep 
      } 
     }); 
    } 

Xem http://msdn.microsoft.com/en-us/library/hh195051.aspx.

+1

Đây chính là điều tôi muốn tránh với câu trả lời của tôi. Có lẽ, tôi là bản thân mình thiếu một cái gì đó:] – Noseratio

+0

@Noseratio Phương pháp async sẽ trở lại ngay lập tức khi một await được nhấn. Trình biên dịch C# thực hiện tất cả đồng bộ hóa luồng cho bạn. http://msdn.microsoft.com/en-us/library/vstudio/hh156528.aspx Quan trọng; "Biểu thức đang chờ đợi không chặn luồng mà nó đang thực thi. Thay vào đó, nó làm cho trình biên dịch đăng ký phần còn lại của phương thức async như là một sự tiếp tục trên task_ đang chờ đợi" – Gusdor

+0

Tôi đã hiểu sai. Chillax. Không có hại trong sửa chữa hoặc thêm rõ ràng, ngay cả sau khi thực tế. Tôi đã chỉnh sửa để tránh nhầm lẫn với những người đọc khác. Tôi không chỉnh sửa để làm cho bạn trông ngu ngốc. Điều đó sẽ vi phạm nguyên tắc DRY./sickburn – Gusdor

0

Bạn không thực sự cần WaitItForWork phương pháp, chỉ cần chờ đợi cho một nhiệm vụ cơ sở dữ liệu khởi tạo:

async Task Run() 
{ 
    await InitializeDatabase(); 
    // Do what you need after database is initialized 
} 

async Task InitializeDatabase() 
{ 
    // Perform database initialization here 
} 

Nếu bạn có nhiều mẩu mã mà gọi để WaitForItToWork thì bạn cần phải quấn cơ sở dữ liệu khởi tạo vào một Task và đang chờ đợi nó trong tất cả các công nhân, ví dụ:

readonly Task _initializeDatabaseTask = InitializeDatabase(); 

async Task Worker1() 
{ 
    await _initializeDatabaseTask; 
    // Do what you need after database is initialized 
} 

async Task Worker2() 
{ 
    await _initializeDatabaseTask; 
    // Do what you need after database is initialized 
} 

static async Task InitializeDatabase() 
{ 
    // Initialize your database here 
} 
0

Chỉ cần cung cấp một giải pháp khác

public static void WaitForCondition(Func<bool> predict) 
    { 
     Task.Delay(TimeSpan.FromMilliseconds(1000)).ContinueWith(_ => 
     { 
      var result = predict(); 
      // the condition result is false, and we need to wait again. 
      if (result == false) 
      { 
       WaitForCondition(predict); 
      } 
     }); 
    } 
Các vấn đề liên quan