Advices in order to add async parts in threaded code

Hello,
I'd like to add an async part in a piece of code and I'm really unable to get an idea about how to do it...

I made a minimal example of what I'm working on (I removed all my attempts to turn this async)

I try to turn the job1 function into async, just spawn it from job1_watch (in a fire & forget fashion) and would like to keep as much as possible of the rest as is... I don't know if it's possible : I made a few attempts, had to use other types of channels etc.

Looks like there are a lot of different implementations in order to achieve async programming... I don't know where to look at.

Best,

use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;

//
// with native threads working version
//

fn main() {
    let (c1_send, c1_rcv): (Sender<u32>, Receiver<u32>) = channel();
    let (c2_send, c2_rcv): (Sender<String>, Receiver<String>) = channel();
    thread::spawn(move || job2_watch(&c2_rcv));
    thread::spawn(move || job1_watch(&c1_rcv, &c2_send));

    let mut range: std::ops::Range<u32> = 0..10000;
    loop {
        match range.next() {
            Some(x) => {
                let _ = c1_send.send(x);
                ()
            }
            None => break,
        }
    }

    loop {}
}

//
// dispatch each received msg to its own threads
fn job1_watch(chan_rcv: &Receiver<u32>, chan_send: &Sender<String>) {
    loop {
        match chan_rcv.recv() {
            Ok(msg) => {
                // I'd like to use async here instead of spawning a new thread
                let send_clone = Sender::clone(chan_send);
                thread::spawn(move || job1(msg, &send_clone));
            }
            _ => {
                continue;
            }
        }
    }
}

// process the received u8
fn job1(src: u32, chan_send: &Sender<String>) {
    let _ = chan_send.send(format!("{}", src));
}

// process the received string
fn job2_watch(chan_rcv: &Receiver<String>) {
    loop {
        match chan_rcv.recv() {
            Ok(p) => {
                println!("{}", p);
            }
            _ => {
                continue;
            }
        }
    }
}

here's what I have, I don't know how far it is for good practices...

async code is absolutely all over the place now... which I'm not super fond of .

use async_std::{
    sync::{channel, Receiver, Sender},
    task,
};
use std::thread;

fn main() {
    let (c1_send, c1_rcv): (Sender<u32>, Receiver<u32>) = channel(1000);
    let (c2_send, c2_rcv): (Sender<String>, Receiver<String>) = channel(1000);

    thread::spawn(move || {
        job2_watch(&c2_rcv);
    });

    thread::spawn(move || {
        job1_watch(&c1_rcv, &c2_send);
    });

    task::block_on(dispatch(&c1_send));

    loop {}
}

async fn dispatch(chan_send: &Sender<u32>) {
    let range: std::ops::Range<u32> = 0..100000;
    for x in range {
        chan_send.send(x).await;
    }
}

//
// dispatch each received msg to its own threads
fn job1_watch(chan_rcv: &Receiver<u32>, chan_send: &Sender<String>) {
    task::block_on(async {
        loop {
            match chan_rcv.recv().await {
                Ok(msg) => {
                    let send_clone = Sender::clone(chan_send);
                    task::spawn(async move {
                        let m = msg.clone();
                        job1(m, &send_clone).await;
                    });
                }
                _ => {
                    ();
                }
            }
        }
    });
}

// process the received u8
async fn job1(src: u32, chan_send: &Sender<String>) {
    let _ = chan_send.send(format!("{}", src)).await;
}

// process the received string
fn job2_watch(chan_rcv: &Receiver<String>) {
    task::block_on(async {
        loop {
            match chan_rcv.recv().await {
                Ok(p) => {
                    println!("{}", p);
                }
                 _ => {
                    continue;
                }
            }
        }
    })
}