是否可以在没有线程的情况下使用异步回调

是否可以在没有线程的情况下使用异步回调

问题描述:

Javascript是解释语言,可以执行异步非阻塞回调,例如:



Javascript is interpreted language and can do asynchronous non-blocking callbacks like:

/* --- in a script tag --- */
function hello9000(msg, callback)
{
        console.log("hello9000 started");
        setTimeout(function()
        {
                callback(msg);
        }, 9000);
        console.log("hello9000 loading");
}

function hello6000(msg, callback)
{
        console.log("hello6000 started");
        setTimeout(function()
        {
                callback(msg);
        }, 6000);
        console.log("hello6000 loading");
}

function hello3000(msg, callback)
{
        console.log("hello3000 started");
        setTimeout(function()
        {
                callback(msg);
        }, 3000);
        console.log("hello3000 loading");
}

hello9000("hello9000 ended", function(msg)
{
        console.log(msg);
});

hello6000("hello6000 ended", function(msg)
{
        console.log(msg);
});

hello3000("hello3000 ended", function(msg)
{
        console.log(msg);
});





C#是用时间语言编译的,你也可以进行异步非阻塞回调。但是有一个问题 - 当我不使用线程时它会阻塞整个代码。我只能手动为每个包含回调的函数创建新线程。



我的代码等效(虽然不是真正等效),但上面是:





C# is compiled just in time language and you can do asynchronous non-blocking callbacks too. But there is one problem - When I do not use threads it's blocking the whole code. I can do it only with manually making new threads for each function containing callback.

My code "equivalent" (although not really equivalent) for the above is:

/* --- in the class --- */
public static void hello9000(string message)
{
        Console.WriteLine("hello9000 loading");
        //this throttling isn't correct
        Stopwatch timer = new Stopwatch();
        timer.Start();
        while (timer.ElapsedMilliseconds < 9000){}
        timer.Stop();
        Console.WriteLine(message);
}
        
public static void hello6000(string message)
{
        Console.WriteLine("hello6000 loading");
        //this throttling isn't correct
        Stopwatch timer = new Stopwatch();
        timer.Start();
        while (timer.ElapsedMilliseconds < 6000){}
        timer.Stop();
        Console.WriteLine(message);
}
        
public static void hello3000(string message)
{
        Console.WriteLine("hello3000 loading");
        //this throttling isn't correct
        Stopwatch timer = new Stopwatch();
        timer.Start();
        while (timer.ElapsedMilliseconds < 3000){}
        timer.Stop();
        Console.WriteLine(message);
}
        
public static void printName(string message, Action<string> callback)
{
        callback(message);
}

/* --- in the main function --- */
/* 
this for instance is blocking
printName("hello9000 ended", hello9000);
printName("hello6000 ended", hello6000);
printName("hello3000 ended", hello3000);
*/
Thread thread9000 = new Thread(new ThreadStart(delegate()
{
        printName("hello9000 ended", hello9000);
}));
Thread thread6000 = new Thread(new ThreadStart(delegate()
{
        printName("hello6000 ended", hello6000);
}));
Thread thread3000 = new Thread(new ThreadStart(delegate()
{
        printName("hello3000 ended", hello3000);
}));

thread9000.Start();
thread6000.Start();
thread3000.Start();





有没有办法做这种类型的回调调用非阻塞而不需要手动制作线程?这是否意味着Javascript在检测到这种模式时会自动创建线程?怎么样的Java - 它的行为就像C#(或C#就像Java一样):





Is there a way of doing this type callback calling non-blocking without the need of making threads manually? Does that mean that Javascript is making threads automatically when detect such pattern? What about Java - it's acting like C# (or C# acts like Java):

//only the threads
Thread hello9000 = new Thread(new Runnable()
{
        public void run()
        {
                System.out.println("hello9000 started");
                try
                {
                        Thread.sleep(9000);
                }
                catch(InterruptedException exc){
                        System.out.println(exc);
                }
                System.out.println("hello9000 ended");
                return;
        }
});
Thread hello6000 = new Thread(new Runnable()
{
        public void run()
        {
                System.out.println("hello6000 started");
                try
                {
                    Thread.sleep(6000);
                }
                catch(InterruptedException exc)
                {
                    System.out.println(exc);
                }
                System.out.println("hello6000 ended");
                return;
        }
});
Thread hello3000 = new Thread(new Runnable()
{
        public void run()
        {
                System.out.println("hello3000 started");
                try
                {
                    Thread.sleep(3000);
                }
                catch(InterruptedException exc)
                {
                    System.out.println(exc);
                }
                System.out.println("hello3000 ended");
                return;
        }
});

hello9000.start();
hello6000.start();
hello3000.start();

你的意思是这样的?这种方法更好的是你可以非常干净地处理异常和超时。

You mean something like this? What's better about this method is you get to handle exceptions as well as timeouts quite cleanly.
private async void StartButton_Click(object sender, EventArgs e)
{
    try
    {
        string message;
        var cancelToken = new CancellationTokenSource();

        cancelToken.CancelAfter(10000);
        message = await WorkerMethod(cancelToken.Token);
        PostMessage(message);

        cancelToken.CancelAfter(2000);
        message = await WorkerMethod(cancelToken.Token);
        PostMessage(message);
    }
    catch (OperationCanceledException)
    {
        PostMessage("Worker was canceled due to timeout.");
    }
    catch (Exception ex)
    {
        PostMessage(string.Format("Worker failed with exception '{0}'.", ex.GetType().ToString()));
    }
}

static async Task<string> WorkerMethod(CancellationToken cancelToken)
{
    // Simulated work... Delays for 5 seconds and returns a message.
    // Of course, if the cancellation token expires first, this task
    // will be canceled before it completes.
    await Task.Delay(5000, cancelToken);

    return "Task completed...";
}

private void PostMessage(string message)
{
    textBox1.Text += message + Environment.NewLine;
}</string>


答案,感谢F-ES Sitecore,John C Rayan,Richard Deeming和Google(仍然过度,但比没有):



The answer, thanks to F-ES Sitecore, John C Rayan, Richard Deeming and Google (still overkill, but better than nothing):

/* --- in the class --- */
public static async Task hello9000(string message)
{
        await Task.Run(delegate()
        {
                Console.WriteLine("hello9000 loading");
                //this throttling isn't correct
                Stopwatch timer = new Stopwatch();
                timer.Start();
                while (timer.ElapsedMilliseconds < 9000){}
                timer.Stop();
                Console.WriteLine(message);
        });
}
        
public static async Task hello6000(string message)
{
        await Task.Run(delegate()
        {
                Console.WriteLine("hello6000 loading");
                //this throttling isn't correct
                 Stopwatch timer = new Stopwatch();
                timer.Start();
                while (timer.ElapsedMilliseconds < 6000){}
                timer.Stop();
                Console.WriteLine(message);
        });
}
        
public static async Task hello3000(string message)
{
        await Task.Run(delegate()
        {
                Console.WriteLine("hello3000 loading");
                //this throttling isn't correct
                Stopwatch timer = new Stopwatch();
                timer.Start();
                while (timer.ElapsedMilliseconds < 3000){}
                timer.Stop();
                Console.WriteLine(message);
        });
}
        
public static async void printName(string message, Func<string, Task> callback)
{
        await callback(message);
}

/* --- in the main function --- */
Task task9000 = printName("hello9000 ended", hello9000);
Task task6000 = printName("hello6000 ended", hello6000);
Task task3000 = printName("hello3000 ended", hello3000);

/* --- the main function doesn't wait the async functions, so additionally you have wait with something like this --- */
Task.WaitAll(task9000, task6000, task3000);
/* --- or separately ---
task9000.Wait();
task6000.Wait();
task3000.Wait();
*/


它实际上比解决方案#1简单得多:

It's actually a lot simpler than Solution #1:
public static async Task HelloDelay(string message, Action<string> callback, int millisecondsDelay)
{
    Console.WriteLine("HelloDelay({0}) loading", millisecondsDelay);
    await Task.Delay(millisecondsDelay);
    callback(message);
}

public static async Task Run()
{
    var task9000 = HelloDelay("HelloDelay(9000) ended", Console.WriteLine, 9000);
    var task6000 = HelloDelay("HelloDelay(6000) ended", Console.WriteLine, 6000);
    var task3000 = HelloDelay("HelloDelay(3000) ended", Console.WriteLine, 3000);
    await Task.WhenAll(task9000, task6000, task3000);
}



使用 Task.Delay 方法 [ ^ ]暂停任务,而不是坐在紧密循环中检查已用时间。



将执行推送到延续,所以你不再需要 Task.Run 来电。



尽可能避免async void:避免异步void方法 - Phil Haack [ ^ ]



我将三个hello方法合并为一个方法,因为它们只是根据延迟时间而变化,可以作为参数传递。如有必要,您可以在不使用 millisecondsDelay 参数的情况下创建该方法的三个副本,并对方法中的延迟进行硬编码。


Use the Task.Delay method[^] to pause the task, rather than sitting in a tight loop checking the elapsed time.

That pushes the execution onto a continuation, so you no longer need the Task.Run call.

Avoid "async void" wherever possible: Avoid async void methods - Phil Haack[^]

I've merged the three "hello" methods into a single method, since they only vary by the delay time, which can be passed as a parameter. If necessary, you could create three copies of the method without the millisecondsDelay parameter, and hard-code the delay within the methods.