如何异步读取和处理多个JSON API响应?

如何异步读取和处理多个JSON API响应?,json,vb.net,winforms,webclient,Json,Vb.net,Winforms,Webclient,我正在从Binance Api读取JSON响应,来自 我需要从中获取一些数据,这是我正在使用的代码: Imports System.Net Imports Newtonsoft.Json Imports System.Collections.Generic Public Class Form1 Private wc As New WebClient() Private wc1 As New WebClient() Private wc2 As New WebClient

我正在从Binance Api读取JSON响应,来自

我需要从中获取一些数据,这是我正在使用的代码:

Imports System.Net
Imports Newtonsoft.Json
Imports System.Collections.Generic

Public Class Form1
    Private wc As New WebClient()
    Private wc1 As New WebClient()
    Private wc2 As New WebClient()
    Private Async Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        Dim btc = Await wc.DownloadStringTaskAsync("https://api.binance.com/api/v1/ticker/24hr?symbol=BTCEUR")
        Dim doge = Await wc1.DownloadStringTaskAsync("https://api.binance.com/api/v1/ticker/24hr?symbol=DOGEEUR")
        Dim bnb = Await wc2.DownloadStringTaskAsync("https://api.binance.com/api/v1/ticker/24hr?symbol=BNBEUR")

        Dim d = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(btc)
        Dim d1 = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(doge)
        Dim d2 = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(bnb)

        Label1.Text = "PRICE " + d("lastPrice")
        Label2.Text = "24H CHANGE " + d("priceChange")
        Label3.Text = "24H CHANGE % " + d("priceChangePercent")
        Label4.Text = "HIGH 24H " + d("highPrice")
        Label5.Text = "LOW 24H " + d("lowPrice")
        Label6.Text = "PRICE " + d1("lastPrice")
        Label7.Text = "24H CHANGE " + d1("priceChange")
        Label8.Text = "24H CHANGE % " + d1("priceChangePercent")
        Label9.Text = "HIGH 24H " + d1("highPrice")
        Label10.Text = "LOW 24H " + d1("lowPrice")
        Label11.Text = "PRICE " + d2("lastPrice")
        Label12.Text = "24H CHANGE " + d2("priceChange")
        Label13.Text = "24H CHANGE % " + d2("priceChangePercent")
        Label14.Text = "HIGH 24H " + d2("highPrice")
        Label15.Text = "LOW 24H " + d2("lowPrice")
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Timer1.Start()
    End Sub
End Class
此代码工作正常,
计时器.Intrval
设置为1000ms,但过了一段时间,我遇到了一个异常:

System.NotSupportedException:WebClient不支持并发I/O操作

行中:

Dim bnb = Await wc2.DownloadStringTaskAsync("https://api.binance.com/api/v1/ticker/24hr?symbol=BNBEUR")
我怎样才能解决它?这似乎没有错,因为我使用了3个不同的WebClient对象来实现这一点


还有,我怎么能只在逗号后显示2位小数?

1000ms可能太快了,wc2.DownloadStringTaskAsync任务可能没有完成。您可以在开始下载任务之前启动计时器,并在任务完成后再次启动计时器:

Private Async Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

    Timer1.Stop

    Dim downloadTasks As New List(Of Task(Of String))

    Dim btc = wc.DownloadStringTaskAsync("https://api.binance.com/api/v1/ticker/24hr?symbol=BTCEUR")
    Dim doge = wc1.DownloadStringTaskAsync("https://api.binance.com/api/v1/ticker/24hr?symbol=DOGEEUR")
    Dim bnb = wc2.DownloadStringTaskAsync("https://api.binance.com/api/v1/ticker/24hr?symbol=BNBEUR")

    downloadTasks.Add(btc)
    downloadTasks.Add(doge)
    downloadTasks.Add(bnb)

    Await Task.WhenAll(downloadTasks)

    Dim d = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(btc.Result)
    Dim d1 = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(doge.Result)
    Dim d2 = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(bnb.Result)

    Label1.Text = "PRICE " + d("lastPrice")
    Label2.Text = "24H CHANGE " + d("priceChange")
    Label3.Text = "24H CHANGE % " + d("priceChangePercent")
    Label4.Text = "HIGH 24H " + d("highPrice")
    Label5.Text = "LOW 24H " + d("lowPrice")
    Label6.Text = "PRICE " + d1("lastPrice")
    Label7.Text = "24H CHANGE " + d1("priceChange")
    Label8.Text = "24H CHANGE % " + d1("priceChangePercent")
    Label9.Text = "HIGH 24H " + d1("highPrice")
    Label10.Text = "LOW 24H " + d1("lowPrice")
    Label11.Text = "PRICE " + d2("lastPrice")
    Label12.Text = "24H CHANGE " + d2("priceChange")
    Label13.Text = "24H CHANGE % " + d2("priceChangePercent")
    Label14.Text = "HIGH 24H " + d2("highPrice")
    Label15.Text = "LOW 24H " + d2("lowPrice")

    Timer1.Start

End Sub
这样您就可以确保以前的下载已经完成

在开始另一次下载之前,您还可以检查WebClient是否仍在忙于使用该属性

至于显示2位小数,请查看。您可以指定一个
NumDigitsAfterDecimal
参数来指示

小数点右边显示多少位。默认值为-1,表示使用计算机的区域设置


1000ms可能太快,
wc2.DownloadStringTaskAsync
任务可能未完成。您可以在开始下载任务之前启动计时器,并在任务完成后再次启动计时器:

Private Async Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

    Timer1.Stop

    Dim downloadTasks As New List(Of Task(Of String))

    Dim btc = wc.DownloadStringTaskAsync("https://api.binance.com/api/v1/ticker/24hr?symbol=BTCEUR")
    Dim doge = wc1.DownloadStringTaskAsync("https://api.binance.com/api/v1/ticker/24hr?symbol=DOGEEUR")
    Dim bnb = wc2.DownloadStringTaskAsync("https://api.binance.com/api/v1/ticker/24hr?symbol=BNBEUR")

    downloadTasks.Add(btc)
    downloadTasks.Add(doge)
    downloadTasks.Add(bnb)

    Await Task.WhenAll(downloadTasks)

    Dim d = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(btc.Result)
    Dim d1 = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(doge.Result)
    Dim d2 = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(bnb.Result)

    Label1.Text = "PRICE " + d("lastPrice")
    Label2.Text = "24H CHANGE " + d("priceChange")
    Label3.Text = "24H CHANGE % " + d("priceChangePercent")
    Label4.Text = "HIGH 24H " + d("highPrice")
    Label5.Text = "LOW 24H " + d("lowPrice")
    Label6.Text = "PRICE " + d1("lastPrice")
    Label7.Text = "24H CHANGE " + d1("priceChange")
    Label8.Text = "24H CHANGE % " + d1("priceChangePercent")
    Label9.Text = "HIGH 24H " + d1("highPrice")
    Label10.Text = "LOW 24H " + d1("lowPrice")
    Label11.Text = "PRICE " + d2("lastPrice")
    Label12.Text = "24H CHANGE " + d2("priceChange")
    Label13.Text = "24H CHANGE % " + d2("priceChangePercent")
    Label14.Text = "HIGH 24H " + d2("highPrice")
    Label15.Text = "LOW 24H " + d2("lowPrice")

    Timer1.Start

End Sub
这样您就可以确保以前的下载已经完成

在开始另一次下载之前,您还可以检查WebClient是否仍在忙于使用该属性

至于显示2位小数,请查看。您可以指定一个
NumDigitsAfterDecimal
参数来指示

小数点右边显示多少位。默认值为-1,表示使用计算机的区域设置


由于您可以调用所有异步方法,因此我建议将API请求移动到一个异步方法,该方法在初始化时会一直向API发送请求(调用之间有一个延迟),直到传递给该方法的请求发出退出的信号。
我将向该方法传递一个委托,该方法负责在aysnc方法启动的任务返回结果时更新UI

委托当然会在UI线程中执行(这里是创建和初始化它的线程)

您可以从可以是aysnc的任何其他方法/事件处理程序运行此方法。这里,例如,按钮的
单击
处理程序。您还可以从
Form.Load
处理程序启动它。或者别的什么

我决定将JSON响应反序列化到类模型,因为有些值需要转换为不同的类型才能有意义。作为返回的日期/时间值,以Unix(毫秒)表示。因此,我使用自定义的
UnixDateTimeConverter
将日期/时间值转换为
DateTimeOffset
结构

Imports System.Net
Imports System.Net.Http
Imports System.Threading
Imports System.Threading.Tasks
Imports Newtonsoft.Json
Imports Newtonsoft.Json.Converters

Private ctsBinance As CancellationTokenSource = Nothing

Private Async Sub SomeButton_Click(sender As Object, e As EventArgs) Handles SomeButton.Click
    ctsBinance = New CancellationTokenSource()

    Dim progressReport = New Progress(Of BinanceResponseRoot())(AddressOf BinanceProgress)
    Try
        ' Pass the Pogress<T> delegate, the delay in ms and the CancellationToken
        Await DownLoadBinanceData(progressReport, 1000, ctsBinance.Token)
    Catch tcEx As TaskCanceledException
        Console.WriteLine("Tasks canceled")
    Finally
        ctsBinance.Dispose()
    End Try
End Sub

Private Sub BinanceProgress(results As BinanceResponseRoot())
    Console.WriteLine("PRICE " & results(0).LastPrice.ToString("N2"))
    Console.WriteLine("24H CHANGE " & results(0).PriceChange.ToString("N2"))
    Console.WriteLine("24H CHANGE % " & results(0).PriceChangePercent.ToString("N2"))
    Console.WriteLine("HIGH 24H " & results(0).HighPrice.ToString("N2"))
    Console.WriteLine("LOW 24H " & results(0).LowPrice.ToString("N2"))
    Console.WriteLine("PRICE " & results(1).LastPrice.ToString("N2"))
    Console.WriteLine("24H CHANGE " & results(1).PriceChange.ToString("N2"))
    Console.WriteLine("24H CHANGE % " & results(1).PriceChangePercent.ToString("N2"))
    Console.WriteLine("HIGH 24H " & results(1).HighPrice.ToString("N2"))
    Console.WriteLine("LOW 24H " & results(1).LowPrice.ToString("N2"))
    Console.WriteLine("PRICE " & results(1).LastPrice.ToString("N2"))
    Console.WriteLine("24H CHANGE " & results(2).PriceChange.ToString("N2"))
    Console.WriteLine("24H CHANGE % " & results(2).PriceChangePercent.ToString("N2"))
    Console.WriteLine("HIGH 24H " & results(2).HighPrice.ToString("N2"))
    Console.WriteLine("LOW 24H " & results(2).LowPrice.ToString("N2"))
End Sub
工作者方法

Public Class BinanceResponseRoot
    Public Property Symbol As String
    Public Property PriceChange As Decimal
    Public Property PriceChangePercent As Decimal
    Public Property WeightedAvgPrice As Decimal
    Public Property PrevClosePrice As Decimal
    Public Property LastPrice As Decimal
    Public Property LastQty As Decimal
    Public Property BidPrice As Decimal
    Public Property BidQty As Decimal
    Public Property AskPrice As Decimal
    Public Property AskQty As Decimal
    Public Property OpenPrice As Decimal
    Public Property HighPrice As Decimal
    Public Property LowPrice As Decimal
    Public Property Volume As Decimal
    Public Property QuoteVolume As Decimal
    <JsonConverter(GetType(BinanceDateConverter))>
    Public Property OpenTime As DateTimeOffset
    <JsonConverter(GetType(BinanceDateConverter))>
    Public Property CloseTime As DateTimeOffset
    Public Property FirstId As Long
    Public Property LastId As Long
    Public Property Count As Long
End Class

Friend Class BinanceDateConverter
    Inherits UnixDateTimeConverter

    Public Overrides Function CanConvert(t As Type) As Boolean
        Return t = GetType(Long) OrElse t = GetType(Long?)
    End Function

    Public Overrides Function ReadJson(reader As JsonReader, t As Type, existingValue As Object, serializer As JsonSerializer) As Object
        Dim uxDT As Long? = serializer.Deserialize(Of Long?)(reader)
        Return DateTimeOffset.FromUnixTimeMilliseconds(uxDT.Value)
    End Function
    Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
        Dim dtmo = DirectCast(value, DateTimeOffset)
        If dtmo <> DateTimeOffset.MinValue Then
            serializer.Serialize(writer, CType(DirectCast(value, DateTimeOffset).ToUnixTimeMilliseconds(), ULong))
        Else
            MyBase.WriteJson(writer, Nothing, serializer)
        End If
    End Sub
End Class
该方法一直并行运行对API的查询,直到请求取消为止,并调用的方法

我使用一个static来发送API请求,因为这更可能是它的工作类型(没有自定义初始化,它使用所有默认值:在某些上下文中,您可能需要初始化HttpClientHandler,作为特定的安全协议)。
将所有任务添加到(任务的)列表中,然后调用执行所有任务

当所有任务返回时,API响应将反序列化到
BinanceResponseRoot
模型,并调用
Progress
委托以使用接收到的信息更新UI

Private Shared binanceClient As New HttpClient()

Public Async Function DownLoadBinanceData(progress As IProgress(Of BinanceResponseRoot()), 
    delay As Integer, token As CancellationToken) As Task

    While Not token.IsCancellationRequested
        Dim tasks As New List(Of Task(Of String))({
            binanceClient.GetStringAsync("https://api.binance.com/api/v1/ticker/24hr?symbol=BTCEUR"),
            binanceClient.GetStringAsync("https://api.binance.com/api/v1/ticker/24hr?symbol=DOGEEUR"),
            binanceClient.GetStringAsync("https://api.binance.com/api/v1/ticker/24hr?symbol=BNBEUR")
        })

        Await Task.WhenAll(tasks)

        Dim btcEur = JsonConvert.DeserializeObject(Of BinanceResponseRoot)(tasks(0).Result)
        Dim dogeEur = JsonConvert.DeserializeObject(Of BinanceResponseRoot)(tasks(1).Result)
        Dim bnbEur = JsonConvert.DeserializeObject(Of BinanceResponseRoot)(tasks(2).Result)

        progress.Report({btcEur, dogeEur, bnbEur})

        Await Task.Delay(delay, token)
    End While
End Function
将JSON数据转换为相应的.Net类型值的类模型

Public Class BinanceResponseRoot
    Public Property Symbol As String
    Public Property PriceChange As Decimal
    Public Property PriceChangePercent As Decimal
    Public Property WeightedAvgPrice As Decimal
    Public Property PrevClosePrice As Decimal
    Public Property LastPrice As Decimal
    Public Property LastQty As Decimal
    Public Property BidPrice As Decimal
    Public Property BidQty As Decimal
    Public Property AskPrice As Decimal
    Public Property AskQty As Decimal
    Public Property OpenPrice As Decimal
    Public Property HighPrice As Decimal
    Public Property LowPrice As Decimal
    Public Property Volume As Decimal
    Public Property QuoteVolume As Decimal
    <JsonConverter(GetType(BinanceDateConverter))>
    Public Property OpenTime As DateTimeOffset
    <JsonConverter(GetType(BinanceDateConverter))>
    Public Property CloseTime As DateTimeOffset
    Public Property FirstId As Long
    Public Property LastId As Long
    Public Property Count As Long
End Class

Friend Class BinanceDateConverter
    Inherits UnixDateTimeConverter

    Public Overrides Function CanConvert(t As Type) As Boolean
        Return t = GetType(Long) OrElse t = GetType(Long?)
    End Function

    Public Overrides Function ReadJson(reader As JsonReader, t As Type, existingValue As Object, serializer As JsonSerializer) As Object
        Dim uxDT As Long? = serializer.Deserialize(Of Long?)(reader)
        Return DateTimeOffset.FromUnixTimeMilliseconds(uxDT.Value)
    End Function
    Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
        Dim dtmo = DirectCast(value, DateTimeOffset)
        If dtmo <> DateTimeOffset.MinValue Then
            serializer.Serialize(writer, CType(DirectCast(value, DateTimeOffset).ToUnixTimeMilliseconds(), ULong))
        Else
            MyBase.WriteJson(writer, Nothing, serializer)
        End If
    End Sub
End Class
公共类BinanceResponseRoot
作为字符串的公共属性符号
公共财产价格变化为十进制
公共财产价格百分比(十进制)
公共财产加权平均价格(十进制)
以十进制表示的公共财产价格
以十进制表示的公共财产价格
公共属性LastQty(十进制)
以十进制表示的公共财产价格
公共财产投标数量(十进制)
公共财产AskPrice(十进制)
公共属性AskQty为十进制
以十进制表示的公共财产开放价格
公共财产价格高达小数点
以十进制表示的公共财产低价
以十进制表示的公共财产卷
公共属性quoteEvolume为十进制
公共属性OpenTime作为DateTimeOffset
公共属性CloseTime作为DateTimeOffset
公共财产第一身份证
公共属性LastId尽可能长
公共财产视为长期资产
末级
友元类二进制数据转换器
继承UnixDateTimeConverter
公共重写函数CanConvert(t作为类型)作为布尔值
返回t=GetType(长)或lse t=GetType(长?)
端函数
Public将函数ReadJson(reader作为JsonReader,t作为Type,existingValue作为Object,serializer作为JsonSerializer)重写为Object
长时变暗uxDT?=序列化程序。反序列化(长?)(读卡器)
返回DateTimeOffset.FromUnixtimeMissions(uxDT.Value)
端函数
Public重写Sub-WriteJson(writer作为JsonWriter,value作为对象,序列化器作为JsonSerializer)
Dim dtmo=DirectCast(值,日期时间偏移)
如果dtmo DateTimeOffset.MinValue,则
serializer.Serialize(writer,CType(DirectCast(值,DateTimeOffset.tounixtimemillizes(),ULong))文件)
其他的
WriteJson(writer,Nothing,serializer)
如果结束
端接头
末级

由于您有所有的异步方法可调用,我建议将API请求移动到一个异步方法,该方法在初始化时会一直向API发送请求(调用之间有一个延迟),直到传递给该方法的请求发出信号为止