My objective is to
start a GUI effect,
await some async work without freezing the GUI
do a final GUI effect
I've prepared a first demo code using a viewmodel with the following
member this.RunSetStatus() =
async {
this.Status <- "!Start resetting #" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
let! task = async {
do! Async.Sleep (10 * 1000)
return "!Reset done #" + DateTime.Now.ToString "yy.MM.dd hh:mm:ss"
}
this.Status <- task
} |> Async.StartImmediate
It behaves as expected so I'm happy with the above.
The issue is when I replace the Sleep in the demo with real blocking work, like a wcf consumer, retrieving some results.
member this.CheckReport(user : string) =
async {
let endpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof<IClaimService>),
new BasicHttpBinding(),
new EndpointAddress(address))
let factory = new ChannelFactory<IClaimService>(endpoint)
let channel = factory.CreateChannel()
let resp = channel.CheckReport(user)
factory.Close()
return resp
}
called from my final delegate command
let RefreshLogic() =
this.RefreshIsActive <- true
async {
let cons = ConsumerLib.ConsumerWCF()
let! task, msg = async {
try
let! resp = cons.CheckReport(Environment.UserName.ToLower())
return resp , ""
with
|exc -> return [||], (ConsumerLib.FindInner(exc).Message + ConsumerLib.FindInner(exc).StackTrace)
}
this.Reports <- task
this.RefreshIsActive <- false
this.StatusMsg <- msg
this.ExportCommand.RaiseCanExecuteChanged()
} |> Async.StartImmediate
It unfortunately freezes the GUI while refreshing (why?)
The problem is your CheckReport function. While it's an async block, it never actually calls any asynchronous work (ie: nothing is bound via let! or do!), so the entire block runs synchronously.
Even though the work is inside of an asynchronous workflow, when you use StartImmediate, the work runs synchronously up to the first actual asynchronous function call, which would be bound by let! or do!. Since your work is completely synchronous, this propogates upwards, and ends up being synchronous, blocking the UI.
If your WCF bindings were setup to include asynchronous versions that are Task returning, the best approach here would be to use the asynchronous version of the WCF method, which would look something like:
let! resp = channel.CheckReportAsync(user) |> Async.AwaitTask
Related
In F# async workflows, we can define a resource that should be cleaned up with the use keyword.
But how does use interact with return?
For example, given this code:
let createResource = async {
use r = Resource ()
do! operationThatMightThrow r
return r
}
async {
use! r = createResource
printfn "%O" r
}
|> Async.RunSynchronously
Where will the calls to Resource.Dispose happen?
How can I design this so that the r is always cleaned up (even if operationThatMightThrow throws)?
I usually have two solutions.
The first solution is actively capturing the exception, manually disposing the disposable object, and re-throwing the exception:
let createResource = async {
let r = new Resource ()
try do! operationThatMightThrow r
with e -> (r :> IDisposable).Dispose(); raise e
return r
}
The second solution is to use a continuation function that will have access to the disposable object before the async returns:
let createResource cont = async {
use r = new Resource ()
do! operationThatMightThrow r
return cont r
}
async {
let! x = createResource (fun r -> printfn "in cont: %O" r)
...
}
They will occur before the value is returned from the computation expression, semantically they will happen in a finally block. If you want to look at the source of the generated using statement, you can find it here. It effectively generates an access-controlled dispose function that calls Dispose() on the resource you pass in, then makes an asynchronous try-finally block with that function in the finally clause.
I have two asynchronous operations, so I have these functions:
// Returs Async<Person>
let GetPerson =
...
// Returs Async<Address>
let GetAddress =
...
What is the idiomatic way to execute them in parallel and get their results?
My starting point is this approach.
let MyFunc = async {
let! person = GetPerson()
let! address = GetAddress()
...
}
This works, but this runs the two operations sequentially.
I also tried this (sort of based on my C# experience).
let MyFunc = async {
let personA = GetPerson()
let addressA = GetAddress()
let! person = personA
let! address = addressA
...
}
But it doesn't work, it also runs the two operations sequentially.
What most of the documentation says is to use Async.Parallel with a sequence, but the problem is that the result type of the two operations are different, so I cannot put them in a sequence.
let MyFunc = async {
let personA = GetPerson()
let addressA = GetAddress()
[personA; addressA]
|> Async.Parallel
...
}
This gives a compilation error, because the two values have different types. (And also, with this syntax, how could I get the actual results?)
What is the idiomatic way to do this?
The idiomatic approach is to start both computations using Async.StartAsChild and then wait for their completion using a second let!:
let MyFunc = async {
let! personWork = GetPerson() |> Async.StartChild
let! addressWork = GetAddress() |> Async.StartChild
let! person = personWork
let! address = addressWork
// (...)
}
Just calling GetPerson does not actually start the work - unlike in C# where tasks are created started, F# workflows are just descriptions of the work to be done, so they need to be started explicitly. The Async.StartChild operation gives you a workflow that starts the work and returns another workflow that can be used for wait for its completion.
I wanna make a web crawling, currently i am reading a txt file with 12000 urls, i wanna use concurrency in this process, but the requests don't work.
typealias escHandler = ( URLResponse?, Data? ) -> Void
func getRequest(url : URL, _ handler : #escaping escHandler){
let session = URLSession(
configuration: .default,
delegate: nil,
delegateQueue: nil)
var request = URLRequest(url:url)
request.httpMethod = "GET"
let task = session.dataTask(with: request){ (data,response,error) in
handler(response,data)
}
task.resume()
}
for sUrl in textFile.components(separatedBy: "\n"){
let url = URL(string: sUrl)!
getRequest(url: url){ response,data in
print("RESPONSE REACHED")
}
}
If you have your URLSessions working correctly, all you need to go is create separate OperationQueue create a Operation for each of your async tasks you want completed, add it to your operation queue, and set your OperationQueue's maxConcurrentOperationCount to control how many of your tasks can run at one time. Puesdo code:
let operationQueue = OperationQueue()
operationQueue.qualityOfService = .utility
let exOperation = BlockOperation(block: {
//Your URLSessions go here.
})
exOperation.completionBlock = {
// A completionBlock if needed
}
operationQueue.addOperation(exOperation)
exOperation.start()
Using a OperationQueue subclass and Operation subclass will give you additional utilities for dealing with multiple threads.
As far as I understood, the use keyword disposes the bound IDisposable as soon it is out of scope, so considering this recursive function:
let rec AsyncAcceptMessages(client : WebSocket) =
async {
let! message = client.AsyncReadMessage
use reader = new StreamReader(message)
let s = reader.ReadToEnd()
printf "%s" <| s
do! AsyncAcceptMessages client
}
Let's pretend that the compiler does not find a way of using tail recursion, would that StreamReader be disposed after each recursion?
UPDATE
Tomas response show me a way of fixing it when you actually expect something back, but what if you are expecting nothing? like in this example with the StreamWriter:
let rec AsyncAcceptMessages(client : WebSocket) =
async {
let! message = client.AsyncReadMessage
if(not(isNull message)) then
let s =
use reader = new StreamReader(message)
reader.ReadToEnd()
use writer = new StreamWriter(client.CreateMessageWriter(WebSocketMessageType.Text), Encoding.UTF8)
writer.Write s
printf "%s" <| s
do! AsyncAcceptMessages client
}
As you're saying, the StreamReader would only be disposed of after the execution returns from the recursive call (i.e. never).
There is another issue, which is that do! is not treated as a tail-recursive call, so if you want to create an infinite tail-recursive loop, you need to use return! (otherwise your code will be leaking memory).
In this case, you can fix it easily using, because you're not doing any asynchronous operations with the StreamReader, so you can just create an ordinary local scope:
let rec AsyncAcceptMessages(client : WebSocket) =
async {
let! message = client.AsyncReadMessage
let s =
use reader = new StreamReader(message)
reader.ReadToEnd()
printf "%s" <| s
return! AsyncAcceptMessages client
}
If you wanted to call e.g. AsyncReadToEnd, then you could do something like:
let rec AsyncAcceptMessages(client : WebSocket) =
async {
let! message = client.AsyncReadMessage
let! s =
async { use reader = new StreamReader(message)
return! reader.ReadToEnd() }
printf "%s" <| s
return! AsyncAcceptMessages client
}
I have been trying to learn F# for the past couple of day and I keep running into something that perplexes me. My "learning project" is a screen scraper for some data I'm kind of interested in manipulating.
In F# PowerPack there is a call Stream.AsyncReadToEnd. I did not want to use the PowerPack just for that single call so I took a look at how they did it.
module Downloader =
open System
open System.IO
open System.Net
open System.Collections
type public BulkDownload(uriList : IEnumerable) =
member this.UriList with get() = uriList
member this.ParalellDownload() =
let Download (uri : Uri) = async {
let UnblockViaNewThread f = async {
do! Async.SwitchToNewThread()
let res = f()
do! Async.SwitchToThreadPool()
return res }
let request = HttpWebRequest.Create(uri)
let! response = request.AsyncGetResponse()
use responseStream = response.GetResponseStream()
use reader = new StreamReader(responseStream)
let! contents = UnblockViaNewThread (fun() -> reader.ReadToEnd())
return uri, contents.ToString().Length }
this.UriList
|> Seq.cast
|> Seq.map Download
|> Async.Parallel
|> Async.RunSynchronously
They have that function UnblockViaNewThread. Is that really the only way to asynchronously read the response stream? Isn't creating a new thread really expensive (I've seen the "~1mb of memory" thrown around all over the place). Is there a better way to do this? Is this what's really happenening in every Async* call (one that I can let!)?
EDIT: I follow Tomas' suggestions and actually came up with something independent of F# PowerTools. Here it is. This really needs error handling, but it asynchronous requests and downloads a url to a byte array.
namespace Downloader
open System
open System.IO
open System.Net
open System.Collections
type public BulkDownload(uriList : IEnumerable) =
member this.UriList with get() = uriList
member this.ParalellDownload() =
let Download (uri : Uri) = async {
let processStreamAsync (stream : Stream) = async {
let outputStream = new MemoryStream()
let buffer = Array.zeroCreate<byte> 0x1000
let completed = ref false
while not (!completed) do
let! bytesRead = stream.AsyncRead(buffer, 0, 0x1000)
if bytesRead = 0 then
completed := true
else
outputStream.Write(buffer, 0, bytesRead)
stream.Close()
return outputStream.ToArray() }
let request = HttpWebRequest.Create(uri)
let! response = request.AsyncGetResponse()
use responseStream = response.GetResponseStream()
let! contents = processStreamAsync responseStream
return uri, contents.Length }
this.UriList
|> Seq.cast
|> Seq.map Download
|> Async.Parallel
|> Async.RunSynchronously
override this.ToString() = String.Join(", ", this.UriList)
I think that AsyncReadToEnd that just synchronously calls ReadToEnd on a separate thread is wrong.
The F# PowerPack also contains a type AsyncStreamReader that contains proper asynchronous implementation of stream reading. It has a ReadLine method that (asynchronously) returns the next line and only downloads a few chunks from the source stream (using the asynchronous ReadAsync as opposed to running on a background thread).
let processStreamAsync stream = async {
use asyncReader = new AsyncStreamReader(stream)
let completed = ref false
while not (!completed) do
// Asynchrnously get the next line
let! nextLine = asyncReader.ReadLine()
if nextLine = null then completed := true
else
(* process the next line *) }
If you want to download the whole content as a string (instead of processing it line-by-line), then you can use ReadToEnd method of AsyncStreamReader. This is a proper asynchronous implementation that starts downloading block of data (asynchronously) and repeats this without blocking.
async {
use asyncReader = new AsyncStreamReader(stream)
return! asyncReader.ReadToEnd() }
Also, F# PowerPack is open-souorce and has permissive license, so the best way to use it is often to just copy the few files you need into your project.