2012-01-27 44 views
7

Các thử nghiệm sau đây cho thấy rằng Async.Sleep trong F # 2.0 không thể bị hủy bỏ ngay lập tức. Chúng tôi sẽ chỉ nhận được thông báo 'hủy' sau khi thời gian trôi qua.là có bất kỳ lý do tại sao Async.Sleep không thể hủy bỏ ngay lập tức?

module async_sleep_test 
    open System 
    open System.Threading 
    open System.Threading.Tasks 
    open System.Xml 

    let cts = new CancellationTokenSource() 
    Task.Factory.StartNew(fun() -> 
     try 
      Async.RunSynchronously(async{ 
       printfn "going to sleep" 
       do! Async.Sleep(10000) 
      }, -1(*no timeout*), cts.Token) 
      printfn "sleep completed" 
     with 
     | :? OperationCanceledException -> 
      printfn "sleep aborted" // we will see it only after 10 sec. 
     | _ -> 
      printfn "sleep raised error" 
    ) |> ignore 
    Thread.Sleep(100) // give time to the task to enter in sleep 
    cts.Cancel() 
    Thread.Sleep(100) // give chance to the task to complete before print bye message 
    printfn "press any key to exit...." 
    Console.ReadKey(true) |> ignore 

Tôi nghĩ rằng đây là hành vi không chính xác. Làm thế nào để bạn nghĩ rằng đây là một lỗi? Sẽ có bất kỳ bất ngờ nếu ví dụ tôi sẽ sử dụng việc thực hiện sau đây:

static member SleepEx(milliseconds:int) = async{ 
    let disp = new SerialDisposable() 
    use! ch = Async.OnCancel(fun()->disp.Dispose()) 
    do! Async.FromContinuations(fun (success, error, cancel) -> 
     let timerSubscription = new SerialDisposable() 
     let CompleteWith = 
      let completed = ref 0 
      fun cont -> 
       if Interlocked.Exchange(completed, 1) = 0 then 
        timerSubscription.Dispose() 
        try cont() with _->() 

     disp.Disposable <- Disposable.Create(fun()-> 
      CompleteWith (fun()-> cancel(new OperationCanceledException())) 
     ) 
     let tmr = new Timer(
      callback = (fun state -> CompleteWith(success)), 
      state = null, dueTime = milliseconds, period = Timeout.Infinite 
     ) 
     if tmr = null then 
      CompleteWith(fun()->error(new Exception("failed to create timer"))) 
     else 
      timerSubscription.Disposable <- Disposable.Create(fun()-> 
       try tmr.Dispose() with _ ->() 
      ) 
    ) 
} 

Trả lời

2

Tôi nghĩ đây là hành vi không chính xác. Làm thế nào để bạn nghĩ rằng đây là một lỗi?

Vâng, tôi nghĩ đó là lỗi. Tôi báo cáo nó như là một lỗi. Microsoft đồng ý đó là một lỗi. Họ đã sửa lỗi trong F # 3.0/VS2012 cùng với các lỗi trong số TryScan và các lỗi khác.

5

Tôi sẽ không nói rằng đây là một lỗi - nó sau từ cách hủy được xử lý trong quy trình công việc F # không đồng bộ nói chung. Nói chung, F # giả định rằng các hoạt động nguyên thủy mà bạn gọi bằng cách sử dụng let! hoặc do! không hỗ trợ hủy (tôi đoán không có cơ chế tiêu chuẩn cho điều đó trong .NET), vì vậy F # chèn kiểm tra hủy trước và sau cuộc gọi được thực hiện bằng cách sử dụng let!.

Vì vậy, một cuộc gọi let! res = foo() thực sự là nhiều hơn như sau (mặc dù kiểm tra được ẩn trong việc thực hiện thư viện của async):

token.ThrowIfCancellationRequested() 
let! res = foo() 
token.ThrowIfCancellationRequested() 

Tất nhiên, công việc được trả về bởi foo() có thể xử lý hủy tốt hơn - thường, nếu nó được thực hiện bằng cách sử dụng một khối async { .. }, sau đó nó sẽ chứa nhiều kiểm tra xung quanh mỗi let!. Tuy nhiên, nói chung (trừ khi một số hoạt động được thực hiện một cách thông minh hơn), việc hủy bỏ sẽ được thực hiện sau khi cuộc gọi let! tiếp theo kết thúc.

định nghĩa thay thế của bạn của Sleep trông khá tốt với tôi - nó hỗ trợ hủy tốt hơn so với một sẵn trong F # thư viện và nếu bạn cần hủy bỏ ngay lập tức, sau đó thay thế 's Async.Sleep với SleepEx của bạn là cách duy nhất để đi F #. Tuy nhiên, có lẽ vẫn sẽ có một số hoạt động không hỗ trợ hủy immmediate, do đó bạn có thể gặp sự cố ở nơi khác (nếu bạn cần hành vi này ở mọi nơi).

PS: Tôi cho rằng chức năng SleepEx của bạn có thể khá hữu ích cho người khác. Nếu bạn có thể chia sẻ nó trên F# Snippets web site, điều đó thật tuyệt vời!

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