首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何异步读取和处理多个JSON响应?

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

Stack Overflow用户
提问于 2021-03-12 21:52:52
回答 2查看 461关注 0票数 0

我正在阅读来自此链接的Binance的JSON响应

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

代码语言:javascript
复制
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

这段代码运行良好,Timer.Intrval设置为1000 is,但过了一会儿,我得到了一个异常:

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

在队伍中:

代码语言:javascript
复制
Dim bnb = Await wc2.DownloadStringTaskAsync("https://api.binance.com/api/v1/ticker/24hr?symbol=BNBEUR")

我该怎么解决呢?这似乎并没有错,因为我使用了3个不同的WebClients对象来完成这个任务。

另外,我怎么能只显示逗号后面的两个小数点呢?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-03-13 01:16:52

由于您有所有的异步方法要调用,我建议将API请求移动到一个异步方法,该方法在初始化时一直向API发送请求--在调用之间存在延迟--直到CancellationToken传递给该方法的信号表明其退出时间为止。

我将一个进展委托传递给该方法,该方法负责在由aysnc方法启动的任务返回其结果时更新UI。

当然,委托在UI线程中执行(无论如何,创建和初始化它的线程)。

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

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

代码语言:javascript
复制
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

若要取消任务的执行,请调用CancellationTokenSourceCancellationTokenSource方法。如果在窗体/窗口关闭之前未取消任务,则在窗体/窗口关闭时调用它,处理该事件。

代码语言:javascript
复制
 ctsBinance?.Cancel()
 ctsBinance = Nothing

工作方法

该方法一直并行地运行对API的查询,直到请求取消为止,调用CancellationTokenSourceCancellationTokenSource方法。

我使用静态HttpClient发送API请求,因为这很可能是它的工作(没有自定义初始化,它使用所有缺省值:您可能需要在某些上下文中初始化HttpClientHandler,作为特定的安全协议)。

将所有HttpClient.GetAsStringAsync()任务添加到List(Of Task)中,然后执行调用Task.WhenAll()的所有任务。

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

代码语言:javascript
复制
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类型值

代码语言:javascript
复制
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
票数 1
EN

Stack Overflow用户

发布于 2021-03-12 23:07:18

1000 is可能太快了,wc2.DownloadStringTaskAsync任务可能还没有完成。您可以在启动这些下载任务之前使用停止播放计时器,并在任务完成时再次执行开始

代码语言:javascript
复制
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是否仍忙于使用WebClient.IsBusy属性。

至于显示两个小数点,请看一下Strings.FormatNumber。您可以指定一个NumDigitsAfterDecimal参数来指示

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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66607699

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档