2014-07-17 12 views
15

Tôi tự hỏi đây có phải là một câu hỏi rộng hay không, nhưng gần đây tôi đã tự mình bắt gặp một đoạn mã mà tôi muốn chắc chắn về cách dịch từ C# vào đúng F #. Cuộc hành trình bắt đầu từ here (1) (vấn đề ban đầu với tương tác TPL-F #) và tiếp tục here (2) (một số mã ví dụ tôi dự tính dịch sang F #).Dịch mã async-await C# thành F # liên quan đến bộ lập lịch

Mã ví dụ quá dài để tái tạo ở đây, nhưng các chức năng thú vị là ActivateAsync, RefreshHubsAddHub. Đặc biệt là những điểm thú vị là

  1. AddHub có chữ ký private async Task AddHub(string address).
  2. RefreshHubs gọi AddHub trong một vòng lặp và thu thập danh sách tasks, sau đó nó sẽ chờ kết thúc vào await Task.WhenAll(tasks) và do đó giá trị trả lại khớp với chữ ký của private async Task RefreshHubs(object _).
  3. RefreshHubs được gọi là ActivateAsync giống như await RefreshHubs(null) và sau đó cuối cùng, hãy gọi await base.ActivateAsync() khớp với chữ ký chức năng public override async Task ActivateAsync().

Câu hỏi:

Điều gì sẽ là bản dịch đúng chữ ký chức năng đó cho F # mà vẫn duy trì giao diện và chức năng và tôn trọng mặc định, tùy chỉnh lịch trình? Và tôi cũng không chắc chắn về điều này "không đồng bộ/chờ đợi trong F #". Như làm thế nào để làm điều đó "máy móc". :)

Lý do là trong liên kết "ở đây (1)" có vẻ là vấn đề (tôi chưa xác minh điều này) trong đó hoạt động không đồng bộ F # không tôn trọng tùy chỉnh, lập lịch hợp tác được đặt bởi (Orleans)) thời gian chạy. Ngoài ra, nó tuyên bố here rằng các hoạt động TPL thoát khỏi lịch trình và đi đến các hồ bơi nhiệm vụ và do đó sử dụng của họ do đó bị cấm.

Một cách tôi có thể nghĩ đối phó với điều này là với một F # chức năng như sau

//Sorry for the inconvenience of shorterned code, for context see the link "here (1)"... 
override this.ActivateAsync() = 
    this.RegisterTimer(new Func<obj, Task>(this.FlushQueue), null, TimeSpan.FromMilliseconds(100.0), TimeSpan.FromMilliseconds(100.0)) |> ignore 

    if RoleEnvironment.IsAvailable then 
     this.RefreshHubs(null) |> Async.awaitPlainTask |> Async.RunSynchronously 
    else 
     this.AddHub("http://localhost:48777/") |> Async.awaitPlainTask |> Async.RunSynchronously 

    //Return value comes from here. 
    base.ActivateAsync() 

member private this.RefreshHubs(_) = 
    //Code omitted, in case mor context is needed, take a look at the link "here (2)", sorry for the inconvinience... 
    //The return value is Task. 
    //In the C# version the AddHub provided tasks are collected and then the 
    //on the last line there is return await Task.WhenAll(newHubAdditionTasks) 
    newHubs |> Array.map(fun i -> this.AddHub(i)) |> Task.WhenAll 

member private this.AddHub(address) = 
    //Code omitted, in case mor context is needed, take a look at the link "here (2)", sorry for the inconvinience... 
    //In the C# version: 
    //... 
    //hubs.Add(address, new Tuple<HubConnection, IHubProxy>(hubConnection, hub)) 
    //} 
    //so this is "void" and could perhaps be Async<void> in F#... 
    //The return value is Task. 
    hubConnection.Start() |> Async.awaitTaskVoid |> Async.RunSynchronously 
    TaskDone.Done 

Các startAsPlainTask chức năng là bởi Sacha Barber từ here. Một tùy chọn khác thú vị có thể here như

module Async = 
    let AwaitTaskVoid : (Task -> Async<unit>) = 
     Async.AwaitIAsyncResult >> Async.Ignore 

< chỉnh sửa: Tôi chỉ nhận thấy Task.WhenAll sẽ cần phải được chờ đợi quá. Nhưng điều gì sẽ là cách thích hợp? Uh, thời gian để ngủ (một sự chơi chữ xấu) ...

< chỉnh sửa 2: Tại here (1) (vấn đề ban đầu với TPL-F # tương tác) trong Codeplex nó đã được đề cập rằng F # sử dụng bối cảnh đồng bộ hóa để thúc đẩy công việc cho chủ đề , trong khi TPL thì không. Bây giờ, đây là một lời giải thích hợp lý, tôi cảm thấy (mặc dù tôi vẫn gặp vấn đề trong việc dịch các đoạn mã này một cách chính xác bất kể người lập lịch tùy chỉnh).Một số thông tin bổ sung thú vị có thể đến thăm từ

Tôi nghĩ rằng tôi cần đề cập đến Hopac trong ngữ cảnh này, như một tiếp tuyến thú vị và cũng đề cập đến tôi không thể tiếp cận trong 50 giờ tiếp theo hoặc lâu hơn trong trường hợp tất cả các bài đăng của tôi bị mất.

< chỉnh sửa 3: Danielsvick đưa ra lời khuyên tốt trong các ý kiến ​​để sử dụng một người thợ xây công việc tùy chỉnh. Daniel cung cấp liên kết đến một liên kết đã được xác định trong FSharpx.

Nhìn vào nguồn tôi thấy giao diện với các thông số được định nghĩa là

type TaskBuilder(?continuationOptions, ?scheduler, ?cancellationToken) = 
    let contOptions = defaultArg continuationOptions TaskContinuationOptions.None 
    let scheduler = defaultArg scheduler TaskScheduler.Default 
    let cancellationToken = defaultArg cancellationToken CancellationToken.None 

Nếu một người sử dụng điều này trong Orleans, nó trông giống như TaskScheduler nên được TaskScheduler.Current theo tài liệu hướng dẫn here

Orleans có công cụ lập lịch nhiệm vụ riêng, cung cấp mô hình thực thi đơn luồng được sử dụng trong các loại ngũ cốc. Điều quan trọng là khi chạy các tác vụ , công cụ lập lịch biểu Orleans được sử dụng và không phải là nhóm chủ đề .NET.

đang ngũ cốc của bạn nên yêu cầu công việc phụ được tạo ra, bạn nên sử dụng Task.Factory.StartNew:

chờ Task.Factory.StartNew (() => {/ * Logic * /});

Kỹ thuật này sẽ sử dụng công cụ lên lịch tác vụ hiện tại, sẽ là công cụ lên lịch Orleans.

Bạn nên tránh sử dụng Task.Run, luôn sử dụng hồ bơi .NET thread và do đó sẽ không chạy trong mô hình đơn lẻ .

Dường như có sự khác biệt nhỏ giữa TaskScheduler.CurrentTaskScheduler.Default. Có lẽ điều này đảm bảo một câu hỏi về trong trường hợp ví dụ sẽ có một sự khác biệt không mong muốn. Như các tài liệu Orleans chỉ ra không sử dụng Task.Run và thay vào đó hướng dẫn để Task.Factory.StartNew, tôi tự hỏi, nếu người ta phải xác định TaskCreationOptions.DenyAttachChild như là khuyến cáo của cơ quan chức năng như Stephen Toub tại Task.Run vs Task.Factory.StartNewStephen Cleary tại StartNew is Dangerous. Hmm, có vẻ như .Default sẽ là .DenyAttachChilld trừ khi tôi bị nhầm lẫn.

Hơn nữa, như có một vấn đề với Task.Run tức Task.Factory.CreateNew về lịch trình tùy chỉnh, tôi tự hỏi, nếu vấn đề cụ thể này có thể được loại bỏ bằng cách sử dụng một tùy chỉnh TaskFactory như được giải thích trong Task Scheduler (Task.Factory) and controlling the number of threadsHow to: Create a Task Scheduler That Limits Concurrency.

Hmm, điều này đang trở nên khá "cân nhắc" lâu rồi. Tôi tự hỏi làm thế nào tôi nên đóng này?Có thể nếu svickDaniel có thể đưa ra nhận xét của họ làm câu trả lời và tôi sẽ upvote cả hai và chấp nhận svick's?

+2

Tôi chưa sử dụng Orleans, nhưng nếu nó yêu cầu chạy tất cả mã async trên bộ lập lịch nhiệm vụ tùy chỉnh mã hóa trong F # sẽ là một nhức đầu vì 'async' không hỗ trợ điều đó. Điều tốt nhất bạn có thể làm (AFAIK) là chuyển đổi mọi 'Task' thành 'Async <'T>' bằng một cái gì đó như 'Async.AwaitTask' (yuck). – Daniel

+0

Thật vậy, đó là những gì tôi đã cố gắng làm và chỉ tránh các luồng công việc không đồng bộ. Than ôi, tôi không chắc liệu tôi đã thành công hay không, dù sao tôi cũng có chút run rẩy về cách dịch một số mẫu C# thành F # (ví dụ tôi nên sử dụng 'async {}' 'và nếu như thế nào, hoặc sau đó lại nếu không) . Thông thường tôi viết bằng một trong hai ngôn ngữ và tôi không có nhu cầu thực sự nghĩ về nó. – Veksi

+0

Tôi không biết F # đủ để bình luận về phần đó, nhưng chỉ muốn chỉ ra những gì tôi tin là một số lỗi trong mã C# ban đầu từ mẫu "ở đây (2)" của bạn. 'Flush' cần phải là một phương thức' async Task' thay vì 'void' vì cả hai' HubConnection.Start' và 'IHupProxy.Invoke' là các phương thức không đồng bộ trả về' Task' sẽ được chờ đợi. Bạn có thể muốn thu thập các nhiệm vụ khi bạn đi qua vòng lặp foreach và thực hiện Task.WhenAll ở cuối phương thức Flush. –

Trả lời

1

Bạn có thể sử dụng TaskBuilder trong FSharpx và chuyển vào TaskScheduler.Current. Đây là nỗ lực của tôi khi dịch RefreshHubs. Lưu ý rằng Task<unit> được sử dụng thay cho Task.

let RefreshHubs _ = 
    let task = TaskBuilder(scheduler = TaskScheduler.Current) 
    task { 
     let addresses = 
      RoleEnvironment.Roles.["GPSTracker.Web"].Instances 
      |> Seq.map (fun instance -> 
       let endpoint = instance.InstanceEndpoints.["InternalSignalR"] 
       sprintf "http://%O" endpoint.IPEndpoint 
      ) 
      |> Seq.toList 

     let newHubs = addresses |> List.filter (not << hubs.ContainsKey) 
     let deadHubs = hubs.Keys |> Seq.filter (fun x -> 
      not (List.exists ((=) x) addresses)) 

     // remove dead hubs 
     deadHubs |> Seq.iter (hubs.Remove >> ignore) 

     // add new hubs 
     let! _ = Task.WhenAll [| for hub in newHubs -> AddHub hub |] 
     return() 
    } 
+0

Đó là một nỗ lực tốt. :) Tôi nghĩ rằng tôi có được ý chính của nó (và mã C# là một chút "oh không" với tất cả các "void FunctionMutatesInternalState()" thứ. Ngay cả trong C# và các lớp học nó cảm thấy dễ dàng hơn để vượt qua các tham số và thu thập các giá trị trả lại và giao cho họ một nơi nào đó Oh, đây là một hoạt động tốt. – Veksi

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