I'm in a little bit of a pickle here. I can't seem to make async workflow to callback at each step of a computation. Here's my code
let criteria: state -> state -> bool = ...//criteria definition
let callback: state -> unit = ..../callback definition
let rec compute task state =
async{
....// some long computations
let task' = task.ContinueWith(fun _ ->callback(newState))
if criteria state newState then return newState
else return! compute task' newState
}
Related
I'm a beginner in rust, and I'm trying to use rust's asynchronous programming.
In my requirement scenario, I want to create a empty Future and complete it in another thread after a complex multi-round scheduling process. The CompletableFuture::complete of Java can meet my needs very well.
I have tried to find an implementation of Rust, but haven't found one yet.
Is it possible to do it in Rust?
I understand from the comments below that using a channel for this is more in line with rust's design.
My scenario is a hierarchical scheduling executor.
For example, Task1 will be splitted to several Drivers, each Driver will use multi thread(rayon threadpool) to do some computation work, and the former driver's state change will trigger the execution of next driver, the result of the whole task is the last driver's output and the intermedia drivers have no output. That is to say, my async function cannot get result from one spawn task directly, so I need a shared stack variable or a channel to transfer the result.
So what I really want is this: the last driver which is executed in a rayon thread, it can get a channel's tx by it's identify without storing it (to simplify the state change process).
I found the tx and rx of oneshot cannot be copies and they are not thread safe, and the send method of tx need ownership. So, I can't store the tx in main thread and let the last driver find it's tx by identify. But I can use mpsc to do that, I worte 2 demos and pasted it into the body of the question, but I have to create mpsc with capacity 1 and close it manually.
I wrote 2 demos, as bellow.I wonder if this is an appropriate and efficient use of mpsc?
Version implemented using oneshot, cannot work.
#[tokio::test]
pub async fn test_async() -> Result<()>{
let mut executor = Executor::new();
let res1 = executor.run(1).await?;
let res2 = executor.run(2).await?;
println!("res1 {}, res2 {}", res1, res2);
Ok(())
}
struct Executor {
pub pool: ThreadPool,
pub txs: Arc<DashMap<i32, RwLock<oneshot::Sender<i32>>>>,
}
impl Executor {
pub fn new() -> Self {
Executor{
pool: ThreadPoolBuilder::new().num_threads(10).build().unwrap(),
txs: Arc::new(DashMap::new()),
}
}
pub async fn run(&mut self, index: i32) -> Result<i32> {
let (tx, rx) = oneshot::channel();
self.txs.insert(index, RwLock::new(tx));
let txs_clone = self.txs.clone();
self.pool.spawn(move || {
let spawn_tx = txs_clone.get(&index).unwrap();
let guard = block_on(spawn_tx.read());
// cannot work, send need ownership, it will cause move of self
guard.send(index);
});
let res = rx.await;
return Ok(res.unwrap());
}
}
Version implemented using mpsc, can work, not sure about performance
#[tokio::test]
pub async fn test_async() -> Result<()>{
let mut executor = Executor::new();
let res1 = executor.run(1).await?;
let res2 = executor.run(2).await?;
println!("res1 {}, res2 {}", res1, res2);
// close channel after task finished
executor.close(1);
executor.close(2);
Ok(())
}
struct Executor {
pub pool: ThreadPool,
pub txs: Arc<DashMap<i32, RwLock<mpsc::Sender<i32>>>>,
}
impl Executor {
pub fn new() -> Self {
Executor{
pool: ThreadPoolBuilder::new().num_threads(10).build().unwrap(),
txs: Arc::new(DashMap::new()),
}
}
pub fn close(&mut self, index:i32) {
self.txs.remove(&index);
}
pub async fn run(&mut self, index: i32) -> Result<i32> {
let (tx, mut rx) = mpsc::channel(1);
self.txs.insert(index, RwLock::new(tx));
let txs_clone = self.txs.clone();
self.pool.spawn(move || {
let spawn_tx = txs_clone.get(&index).unwrap();
let guard = block_on(spawn_tx.value().read());
block_on(guard.deref().send(index));
});
// 0 mock invalid value
let mut res:i32 = 0;
while let Some(data) = rx.recv().await {
println!("recv data {}", data);
res = data;
break;
}
return Ok(res);
}
}
Disclaimer: It's really hard to picture what you are attempting to achieve, because the examples provided are trivial to solve, with no justification for the added complexity (DashMap). As such, this answer will be progressive, though it will remain focused on solving the problem you demonstrated you had, and not necessarily the problem you're thinking of... as I have no crystal ball.
We'll be using the following Result type in the examples:
type Result<T> = Result<T, Box<dyn Error + Send + Sync + 'static>>;
Serial execution
The simplest way to execute a task, is to do so right here, right now.
impl Executor {
pub async fn run<F>(&self, task: F) -> Result<i32>
where
F: FnOnce() -> Future<Output = Result<i32>>,
{
task().await
}
}
Async execution - built-in
When the execution of a task may involve heavy-weight calculations, it may be beneficial to execute it on a background thread.
Whichever runtime you are using probably supports this functionality, I'll demonstrate with tokio:
impl Executor {
pub async fn run<F>(&self, task: F) -> Result<i32>
where
F: FnOnce() -> Result<i32>,
{
Ok(tokio::task::spawn_block(task).await??)
}
}
Async execution - one-shot
If you wish to have more control on the number of CPU-bound threads, either to limit them, or to partition the CPUs of the machine for different needs, then the async runtime may not be configurable enough and you may prefer to use a thread-pool instead.
In this case, synchronization back with the runtime can be achieved via channels, the simplest of which being the oneshot channel.
impl Executor {
pub async fn run<F>(&self, task: F) -> Result<i32>
where
F: FnOnce() -> Result<i32>,
{
let (tx, mut rx) = oneshot::channel();
self.pool.spawn(move || {
let result = task();
// Decide on how to handle the fact that nobody will read the result.
let _ = tx.send(result);
});
Ok(rx.await??)
}
}
Note that in all of the above solutions, task remains agnostic as to how it's executed. This is a property you should strive for, as it makes it easier to change the way execution is handled in the future by more neatly separating the two concepts.
I want to establish an http connection to a Ganache test blockchain.
Going through the GitHub page of the web3 crate I found this example:
#[tokio::main]
async fn main() -> web3::Result<()> {
let _ = env_logger::try_init();
let transport = web3::transports::Http::new("http://localhost:7545")?;
let web3 = web3::Web3::new(transport);
let mut accounts = web3.eth().accounts().await?;
...
Ok(())
}
However I want to implement the connection setup in a function. So I tried the following:
async fn establish_web3_connection_http(url: &str) -> web3::Result<Web3<Http>>{
let transport = web3::transports::Http::new(url)?;
Ok(web3::Web3::new(transport))
}
...
#[tokio::main]
async fn main() -> web3::Result<()> {
let web3_con = establish_web3_connection_http("http://localhost:7545");
println!("Calling accounts.");
let mut accounts = web3_con.eth().accounts().await?;
Ok(())
}
This results in the following error:
Error
I am not sure why I do not return the correct value. There is not error when I
don't call web3_con, so the function seems to be fine.
Is the return value somehow wrong, or how I call it?
establish_web3_connection_http() is an async function, so it returns a future. You're trying to call .eth() on the future, when you probably want to call it on the value produced by the future. You need to await the result of this function:
let web3_con = establish_web3_connection_http("http://localhost:7545").await?;
// ^^^^^^^
However, you don't do any awaiting in establish_web3_connection_http(), so there's no reason it needs to be async in the first place. You could just remove async from its signature instead:
fn establish_web3_connection_http(url: &str) -> web3::Result<Web3<Http>>{
Rust has async methods that can be tied to Abortable futures. The documentation says that, when aborted:
the future will complete immediately without making any further progress.
Will the variables owned by the task bound to the future be dropped? If those variables implement drop, will drop be called? If the future has spawned other futures, will all of them be aborted in a chain?
E.g.: In the following snippet, I don't see the destructor happening for the aborted task, but I don't know if it is not called or happens in a separate thread where the print is not shown.
use futures::executor::block_on;
use futures::future::{AbortHandle, Abortable};
struct S {
i: i32,
}
impl Drop for S {
fn drop(&mut self) {
println!("dropping S");
}
}
async fn f() -> i32 {
let s = S { i: 42 };
std::thread::sleep(std::time::Duration::from_secs(2));
s.i
}
fn main() {
println!("first test...");
let (abort_handle, abort_registration) = AbortHandle::new_pair();
let _ = Abortable::new(f(), abort_registration);
abort_handle.abort();
std::thread::sleep(std::time::Duration::from_secs(1));
println!("second test...");
let (_, abort_registration) = AbortHandle::new_pair();
let task = Abortable::new(f(), abort_registration);
block_on(task).unwrap();
std::thread::sleep(std::time::Duration::from_secs(1));
}
playground
Yes, values that have been created will be dropped.
In your first example, the future returned by f is never started, so the S is never created. This means that it cannot be dropped.
In the second example, the value is dropped.
This is more obvious if you both run the future and abort it. Here, I spawn two concurrent futures:
create an S and waits 200ms
wait 100ms and abort future #1
use futures::future::{self, AbortHandle, Abortable};
use std::time::Duration;
use tokio::time;
struct S {
i: i32,
}
impl S {
fn new(i: i32) -> Self {
println!("Creating S {}", i);
S { i }
}
}
impl Drop for S {
fn drop(&mut self) {
println!("Dropping S {}", self.i);
}
}
#[tokio::main]
async fn main() {
let create_s = async {
let s = S::new(42);
time::delay_for(Duration::from_millis(200)).await;
println!("Creating {} done", s.i);
};
let (abort_handle, abort_registration) = AbortHandle::new_pair();
let create_s = Abortable::new(create_s, abort_registration);
let abort_s = async move {
time::delay_for(Duration::from_millis(100)).await;
abort_handle.abort();
};
let c = tokio::spawn(create_s);
let a = tokio::spawn(abort_s);
let (c, a) = future::join(c, a).await;
println!("{:?}, {:?}", c, a);
}
Creating S 42
Dropping S 42
Ok(Err(Aborted)), Ok(())
Note that I've switched to Tokio to be able to use time::delay_for, as you should never use blocking operations in an async function.
See also:
Why does Future::select choose the future with a longer sleep period first?
What is the best approach to encapsulate blocking I/O in future-rs?
If the future has spawned other futures, will all of them be aborted in a chain?
No, when you spawn a future, it is disconnected from where it was spawned.
See also:
What is the purpose of async/await in Rust?
I am running a simple chat app with f#. In the chat when one user types "exit" then I want both clients to finish the chat. Currently I am running in the console, and so read and write are blocking, but I am using a class to wrap the console so there is no async problems.
(In the following code the sendUI and reciveUI are async functions that send and recieve messages over the wire)
type IConnection =
abstract Send : string -> Async<bool>
abstract Recieve : unit -> Async<string>
abstract Connected : bool
abstract Close : unit -> unit
type IOutput =
abstract ClearLine : unit -> unit
abstract ReadLine : ?erase:bool -> string
abstract WriteLine : string -> unit
let sendUI (outputer:#IOutput) (tcpConn: #IConnection) () =
async {
if not tcpConn.Connected then return false
else
let message = outputer.ReadLine(true)
try
match message with
| "exit" -> do! tcpConn.Send "exit" |> Async.Ignore
return false
| _ -> if message.Trim() <> ""
then do! message.Trim() |> tcpConn.Send |> Async.Ignore
outputer.WriteLine("me: " + message)
return true
with
| e -> outputer.WriteLine("log: " + e.Message)
return false
}
let recieveUI (outputer:#IOutput) (tcpConn: #IConnection) () =
async {
if not tcpConn.Connected then return false
else
try
let! response = tcpConn.Recieve()
match response with
| "exit" -> return false
| _ -> outputer.WriteLine("other: " + response)
return true
with
| e -> outputer.WriteLine("error: " + e.Message)
return false
}
let rec loop (cancel:CancellationTokenSource) f =
async {
match! f() with
| false -> cancel.Cancel(true)
| true -> do! loop cancel f
}
let messaging recieve send (outputer: #IOutput) (tcpConn:#IConnection) =
printfn "write: exit to exit"
use cancelSrc = new CancellationTokenSource()
let task =
[ recieve outputer tcpConn
send outputer tcpConn ]
|> List.map (loop cancelSrc)
|> Async.Parallel
|> Async.Ignore
try
Async.RunSynchronously (computation=task, cancellationToken=cancelSrc.Token)
with
| :? OperationCanceledException ->
tcpConn.Close()
let exampleReceive =
{ new IConnection with
member this.Connected = true
member this.Recieve() = async { do! Async.Sleep 1000
return "exit" }
member this.Send(arg1) = async { return true }
member this.Close() = ()
}
let exampleOutputer =
{ new IOutput with
member this.ClearLine() = raise (System.NotImplementedException())
member this.ReadLine(erase) = Console.ReadLine()
member this.WriteLine(arg) = Console.WriteLine(arg) }
[<EntryPoint>]
let main args =
messaging recieveUI sendUI exampleOutputer exampleReceive
0
(I wrapped the console with an object so i wont get weird things on screen: outputer)
When I get "exit" over the wire i return false and so the loop calls cancel so it should also stop the sending messages async computation.
However, when I do this, the sendUI gets stuck:
async {
//do stuff
let message = Console.ReadLine() //BLOCKS! doesn't cancel
//do stuff
}
One fix would be to somehow make Console.ReadLine() an async, however the simple async { return ...} does not work.
I also tried running it as a task and calling Async.AwaitTask, but this does not work either!
I read that one can use Async.FromContinuations but I couldn't figure out how to use it (and what I tried didn't solve it...)
Little help?
EDIT
The reason this doesn't simply work is because the way async computations cancellation work. They check whether to cancel when it reaches a let!/do!/return! etc, and so the solutions above do not work.
EDIT 2
Added runnable code sample
You can wrap the Console.ReadLine in its own async, then call that with Async.RunSynchronously and a CancellationToken. This will allow you to cancel that blocking operation, because it won't be on the same thread as the console itself.
open System
open System.Threading
type ITcpConnection =
abstract member Send: string -> unit
let readLineAsync cancellation =
async {
try
return Some <| Async.RunSynchronously(async { return Console.ReadLine() }, cancellationToken = cancellation)
with | _ ->
return None
}
let receiveUI cancellation (tcpConnection: ITcpConnection) =
let rec loop () =
async {
let! message = readLineAsync cancellation
match message with
| Some msg -> msg |> tcpConnection.Send
| None -> printfn "Chat Session Ended"
return! loop ()
}
loop () |> Async.Start
I'm trying to use Fsharpx' Async.AwaitObservable inside an async workflow which is started using Async.StartWithContinuations. For some reason, if the cancellation token used to start this workflow is canceled while it is waiting for the observable (but not during other parts of the workflow), the cancellation continuation is never called. However, if I put it inside a use! __ = Async.OnCancel (interruption), then the interruption function does get called. Can someone please clarify why this happens and what the best way is to do this and make sure that one of the continuation functions always gets called?
open System
open System.Reactive.Linq
open FSharp.Control.Observable
open System.Threading
[<EntryPoint>]
let main _ =
let cancellationCapability = new CancellationTokenSource()
let tick = Observable.Interval(TimeSpan.FromSeconds 1.0)
let test = async {
let! __ = Async.AwaitObservable tick
printfn "Got a thing." }
Async.StartWithContinuations(test,
(fun () -> printfn "Finished"),
(fun exn -> printfn "Error!"),
(fun exn -> printfn "Canceled!"),
cancellationCapability.Token)
Thread.Sleep 100
printfn "Cancelling..."
cancellationCapability.Cancel()
Console.ReadLine() |> ignore
0 // return an integer exit code
It seems to me as well that it's a problem in how AwaitObservable is implemented. Good luck on fixing that.
That said, one workaround that you can use on your client side code is wrapping the AwaitObservable in a Task:
async {
let! ct = Async.CancellationToken
let! __ =
Async.StartAsTask(Async.AwaitObservable tick, cancellationToken = ct)
|> Async.AwaitTask
printfn "Got a thing."
}
Not ideal, but works.
It seems that the version of Fsharpx on GitHub already contains a fix (not implemented by me). However the current version on NuGet (1.8.41) has not been updated to include this fix. See the change here.
EDIT 1:
The code on GitHub also has some issues with Observables with replay semantics. I have fixed this for now like so but hopefully there is a cleaner solution. I will submit a PR after I think about whether there is a way to make it simpler.
/// Creates an asynchronous workflow that will be resumed when the
/// specified observables produces a value. The workflow will return
/// the value produced by the observable.
static member AwaitObservable(observable : IObservable<'T1>) =
let removeObj : IDisposable option ref = ref None
let removeLock = new obj()
let setRemover r =
lock removeLock (fun () -> removeObj := Some r)
let remove() =
lock removeLock (fun () ->
match !removeObj with
| Some d -> removeObj := None
d.Dispose()
| None -> ())
synchronize (fun f ->
let workflow =
Async.FromContinuations((fun (cont,econt,ccont) ->
let rec finish cont value =
remove()
f (fun () -> cont value)
setRemover <|
observable.Subscribe
({ new IObserver<_> with
member x.OnNext(v) = finish cont v
member x.OnError(e) = finish econt e
member x.OnCompleted() =
let msg = "Cancelling the workflow, because the Observable awaited using AwaitObservable has completed."
finish ccont (new System.OperationCanceledException(msg)) })
() ))
async {
let! cToken = Async.CancellationToken
let token : CancellationToken = cToken
#if NET40
use registration = token.Register(fun () -> remove())
#else
use registration = token.Register((fun _ -> remove()), null)
#endif
return! workflow
})
static member AwaitObservable(observable : IObservable<'T1>) =
let synchronize f =
let ctx = System.Threading.SynchronizationContext.Current
f (fun g ->
let nctx = System.Threading.SynchronizationContext.Current
if ctx <> null && ctx <> nctx then ctx.Post((fun _ -> g()), null)
else g() )
let continued = ref false
let continuedLock = new obj()
let removeObj : IDisposable option ref = ref None
let removeLock = new obj()
let setRemover r =
lock removeLock (fun () -> removeObj := Some r)
let remove() =
lock removeLock (fun () ->
match !removeObj with
| Some d ->
removeObj := None
d.Dispose()
| None -> ())
synchronize (fun f ->
let workflow =
Async.FromContinuations((fun (cont,econt,ccont) ->
let rec finish cont value =
remove()
f (fun () -> lock continuedLock (fun () ->
if not !continued then
cont value
continued := true))
let observer =
observable.Subscribe
({ new IObserver<_> with
member __.OnNext(v) = finish cont v
member __.OnError(e) = finish econt e
member __.OnCompleted() =
let msg = "Cancelling the workflow, because the Observable awaited using AwaitObservable has completed."
finish ccont (new System.OperationCanceledException(msg)) })
lock continuedLock (fun () -> if not !continued then setRemover observer else observer.Dispose())
() ))
async {
let! cToken = Async.CancellationToken
let token : CancellationToken = cToken
use __ = token.Register((fun _ -> remove()), null)
return! workflow
})
EDIT 2:
Neater fix for the hot observable issue...
let AwaitObservable(observable : IObservable<'T>) = async {
let! token = Async.CancellationToken // capture the current cancellation token
return! Async.FromContinuations(fun (cont, econt, ccont) ->
// start a new mailbox processor which will await the result
Agent.Start((fun (mailbox : Agent<Choice<'T, exn, OperationCanceledException>>) ->
async {
// register a callback with the cancellation token which posts a cancellation message
#if NET40
use __ = token.Register((fun _ ->
mailbox.Post (Choice3Of3 (new OperationCanceledException("The opeartion was cancelled.")))))
#else
use __ = token.Register((fun _ ->
mailbox.Post (Choice3Of3 (new OperationCanceledException("The opeartion was cancelled.")))), null)
#endif
// subscribe to the observable: if an error occurs post an error message and post the result otherwise
use __ =
observable.FirstAsync()
.Catch(fun exn -> mailbox.Post(Choice2Of3 exn) ; Observable.Empty())
.Subscribe(fun result -> mailbox.Post(Choice1Of3 result))
// wait for the first of these messages and call the appropriate continuation function
let! message = mailbox.Receive()
match message with
| Choice1Of3 reply -> cont reply
| Choice2Of3 exn -> econt exn
| Choice3Of3 exn -> ccont exn })) |> ignore) }