从 F# 调用 C# 异步方法会导致死锁 [英] Calling C# async method from F# results in a deadlock

查看:20
本文介绍了从 F# 调用 C# 异步方法会导致死锁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一组 F# 脚本,它们调用我们创建的各种库,其中许多公开了最初用 C# 编写的异步方法.最近我发现脚本停止工作了(我想距离我上次使用它们大约有半年时间,然后他们工作了).

I have a set of F# scripts that call various libraries that we have created, many of them exposing asynchronous methods originally written in C#. Recently I found out the scripts stopped working (I think it's about half a year since I used them last time and they worked back then).

我试图隔离问题并想出了以下重现它的代码:

I was trying to isolate the problem and came up with the following code that reproduces it:

首先,让我们考虑一个包含以下 C# 类的库:

First, let's consider a library containing the following C# class:

    public class AsyncClass
    {
        public async Task<string> GetStringAsync()
        {
            var uri = new Uri("https://www.google.com");
            var client = new HttpClient();
            var response = await client.GetAsync(uri);
            var body = await response.Content.ReadAsStringAsync();
            return body;
        }
    }

接下来,让我们使用以下代码从 F#(FSX 脚本)调用库:

Next, let's call the library from F# (FSX script) using the following code:

let asyncClient = AsyncClass()

let strval1 = asyncClient.GetStringAsync() |> Async.AwaitTask |> Async.RunSynchronously
printfn "%s" strval1

let strval2 = 
    async {
        return! asyncClient.GetStringAsync() |> Async.AwaitTask
    } |> Async.RunSynchronously
printfn "%s" strval2

获得 strval1 以死锁告终,而 strval2 被检索得很好(我很确定第一个场景在几个月前也能正常工作,所以它看起来可能是某种更新导致了这种情况).

Obtaining strval1 ends up with a deadlock, whereas strval2 is retrieved just fine (I am quite sure the first scenario used to work too a couple of months ago so it looks like some sort of an update might have caused this).

这很可能是一个同步上下文问题,线程基本上等待自己完成",但我不明白第一次调用到底有什么问题 - 我看不出有什么问题.

This is most likely a synchronisation context issue where the thread is basically "waiting for itself to finish", but I don't understand what exactly is wrong with the first call - I can't see anything wrong with it.

StackOverflow 上的类似问题:

Similar issues on StackOverflow:

  • Why do I have to wrap an Async<T> into another async workflow and let! it? - this seems to be the same question, but not enough information is given and a simple reproducing example is missing
  • Why is Async.RunSynchronously hanging? - this is similar but there is an obvious mistake the author has made

推荐答案

所以 .net Task 将立即启动,而 F# async {} 是惰性的.因此,当您将任务包装在 async { } 中时,它会变得懒惰,因此将具有 Async.RunSynchronously 所期望的特征.

So a .net Task will start immediately, while F# async {} is lazy. So when you wrap a task inside an async { } it becomes lazy, and thus will have the characteristics that Async.RunSynchronously is expecting.

通常我只在执行 f# 异步操作时使用 async {},如果我使用 .net 任务,我将使用 TaskBuilder.fs(在 nuget 中可用).它更了解 Task 特性,例如 ConfigureAwait(continueOnCapturedContext: false).

Generally I use async {} when I'm doing f# asynchronous operations only, and if I'm working with .net Tasks I'll use TaskBuilder.fs (available in nuget). It's more aware of Task idiosyncrasies like ConfigureAwait(continueOnCapturedContext: false).

open FSharp.Control.Tasks.V2.ContextInsensitive

task {
    let! x = asyncClient.GetStringAsync()
    //do something with x
}

这篇关于从 F# 调用 C# 异步方法会导致死锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆