下面的代碼曾經可以作業 - 但我正在將我的很多 async{} 函式轉換為 task{} 并且我無法弄清楚如何在沒有可變變數的情況下使其按順序運行。
let processBatchTransaction(page: IPage,
bnzBatch: BNZBatch,
cardTransactionswithOrder: BNZCardWithOrder list,
xeroClient: XeroClientFull) : Task<StatusMessage> = task {
let matchedTransactionsSeq
= seq{
for cardTransactionWithOrder in cardTransactionswithOrder do
let matchedTransaction = matchTransaction(page,bnzBatch,cardTransactionWithOrder, xeroClient)
yield matchedTransaction
}
let! matchedTransactions = matchedTransactionsSeq |> Async.Sequential
.... etc
}
函式“matchTransaction”曾經是異步的——但沒有Task.Sequential,只有Task.WhenAll,但我需要它們一個接一個地同步運行。
uj5u.com熱心網友回復:
編輯:請參閱下面的所有方式,以獲得更簡單、更慣用的解決方案。該行下方的原始答案。
正如所承諾的(盡管布賴恩已經給出了很好的答案),這是我的。讓我們首先了解一些規則:
- 創建的任務
task { ... }
將始終是熱啟動的。這意味著它會立即在當前執行緒(或后臺執行緒,如果您使用backgroundTask
)上運行。 - 通過 TPL 創建的帶有
Task<_>
類的任務將始終延遲啟動。就像async
你必須手動啟動這些一樣。 task
在CE中系結任何任務都將啟動該任務。- 下一個系結運算式只會在前一個系結運算式完成后開始。
這意味著,給定一個帶有Task
s 的陣列或序列,它們很可能在你得到它們時已經在運行。而且由于它們不是通過創建的bind
,它們將異步運行。為了防止這種情況,我們需要一些規則。
編輯:如果您還沒有,請查看F# ,它可以開箱即用地執行以下操作。
規則 0:處理任務時,總是回傳一個task
組合任務、系結任務或加入任務時,始終將結果回傳為task
. 這將簡化您的整體流程,此外,您不能在不阻塞執行緒的情況下回傳非任務(或非異步)。
規則 1:延遲你的任務
Brian 建議通過lazy
. 這可以。您也可以簡單地使用單位函式:fun() -> task { dosomething }
.
規則 2:不要使用.Result
or.Wait()
這些會阻塞你的執行緒。
規則 3:不要使用類似的東西List.traverseTaskResultA
這些函式是其他優秀FsToolkit.ErrorHandling.TaskResult
庫的一部分,不會像run X -> wait for result -> run Y -> wait for result
. 相反,他們做到了run X -> run Y -> run Z -> asTask
。
換句話說,那些庫函式會導致異步的、重疊的執行。
規則 4:不要使用Thread.Sleep
幾乎沒有規則,但是當我測驗你的場景時,我使用了Thread.Sleep
. 問題是,這個函式阻塞了當前執行緒。它會給人一種錯誤的印象,即您的函式在您的測驗場景中按順序運行,但在您的實際場景中它們不再如此。
改為使用Task.Delay
。它的作業原理相同,但不會阻塞執行緒。
解決方案
有多種方法可以做到這一點。我將只向您展示一個使用ContinueWith
. 腳步:
- 您的任務必須延遲回傳,作為單元函式
- 您的任務不依賴于彼此的結果(但這很容易改變)
- 將您的任務包裝在一個延續中,然后
Unwrap
再次延續。
最后一個似乎有點奇怪,但 TPL 沒有附帶標準的bind
. 而bind
來自 F# 的雖然適用于此,但使用起來有點困難。該Ply
庫帶有一個更簡單的系結,您也可以嘗試一下。
無論如何,這是硬核方式,僅使用標準庫函式。好訊息是,您只需撰寫一次此函式。
/// Join multiple delayed tasks and return the result of the last
let join tasks =
let wrapNext (t: unit -> Task<_>) (source: unit -> Task<_>): unit -> Task<_> =
fun () ->
source()
// this is the CORE of the whole operation
.ContinueWith((fun (_: Task) -> t ()), TaskContinuationOptions.OnlyOnRanToCompletion)
// extra step needed, as BCL has no direct way to unwrap nested tasks
.Unwrap() :?> Task<_>
let rec combine acc (tasks: (unit -> Task<_>) list) =
match tasks with
| [] -> acc
| t :: tail -> combine (wrapNext t acc) tail
match tasks with
| first :: rest -> combine first rest
| [] -> failwith "oh oh, no tasks given!"
這是您可以使用它的方法。第一個函式看起來有點笨拙,但它只是為了模仿您的場景并能夠仔細檢查它是否按順序運行。
/// Create 10 tasks, use stream for writing, otherwise would garble FSI
/// Note that those can be task or backgroundTask
let createBunchOfTasks(sw: StreamWriter) =
let mutable x = 0
let rnd () = Random().Next(10, 30)
let o = obj ()
let runTask i = backgroundTask {
let! _ = Task.Delay(rnd ()) // use randomization to ensure tasks last different times
x <- x 1
// just some logging to a file, stdout is not good in this case
lock o (fun () -> sw.WriteLine(sprintf "Task #%i after delay: %i" i x))
return x
}
[
// creating bunch of dummy tasks
for i in 0..10 do
fun () -> runTask i
]
在您的場景中,您不需要上面的代碼,但您需要類似的東西來呼叫該join
函式。如果你需要記錄到一個檔案,你可以使用這個模式,但它實際上只是為了我的健全性檢查,而且相當粗糙;)。
let runMultipleTasks() =
// should give this as argument, as must be closed after all tasks completed
let file = File.Open("output1.txt", FileMode.Create)
let stream = new StreamWriter(file)
// the actual creation of tasks
let tasks = createBunchOfTasks stream
let combinedTask = join tasks
// start the combined tasks
combinedTask()
更簡單,更慣用的解決方案
在對任務串列、序列或陣列進行了更多修改之后,主要問題是它們必須被延遲。事實證明,您可以task
使用for
.
但是,您必須注意不要只使用yield
,而是要let!
與 結合使用yield
。這將確保任務將按順序等待:
let runMultipleTasks() = task {
// should give this as argument, as must be closed after all tasks completed
let file = File.Open("output1.txt", FileMode.Create)
let stream = new StreamWriter(file)
// the actual creation of tasks
let tasks = createBunchOfTasks stream |> Array.ofList
let len = Array.length tasks
let results = Array.zeroCreate len
for i in 0..len - 1 do
let! result = tasks[i]() // ensure await-on-next
results[i] <- result
return List.ofArray results
}
注意:該庫IcedTasks
有一種ColdTask
型別,它允許以更簡單的方式進行上述操作,就好像它是一項正常任務一樣,并確保冷啟動。
uj5u.com熱心網友回復:
您可以做的一件事是使用惰性來防止任務過早開始。例如:
let createTask n =
task {
printfn $"Task {n} started"
Thread.Sleep(2000)
printfn $"Task {n} finished"
return 100 n
}
let lazyTasks =
seq {
for n = 1 to 10 do
lazy createTask n
}
所以在你的情況下,你會寫:
for cardTransactionWithOrder in cardTransactionswithOrder do
let lazyMatchedTransaction = lazy matchTransaction(page,bnzBatch,cardTransactionWithOrder, xeroClient)
yield lazyMatchedTransaction
然后,您可以像這樣按順序評估任何惰性任務序列:
let evalTasksSequential lazyTasks =
task {
return seq {
for (lazyTask : Lazy<Task<_>>) in lazyTasks do
yield lazyTask.Value.Result // start task and wait for it to finish
}
}
測驗示例:
task {
let! results = evalTasksSequential lazyTasks
for result in results do
printfn $"{result}"
} |> ignore
輸出:
Task 1 started
Task 1 finished
101
Task 2 started
Task 2 finished
102
Task 3 started
Task 3 finished
103
Task 4 started
Task 4 finished
104
Task 5 started
Task 5 finished
105
Task 6 started
Task 6 finished
106
Task 7 started
Task 7 finished
107
Task 8 started
Task 8 finished
108
Task 9 started
Task 9 finished
109
Task 10 started
Task 10 finished
110
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/508499.html