我正在從某種產品 API 請求資料,但問題是我得到了 20 乘 20。所以端點看起來像這樣:
https://www.someDummyAPI.com/Api/Products?offset=0&count=20
注意:我無法更改計數,它將始終為 20。
即來自該端點的資料將包含 20 條記錄,從 0 到 20,之后我必須將偏移量增加 20 以獲得下一個 20 條記錄,依此類推(總共大約 1500 條記錄,所以我必須發出大約 700 個請求)。
獲取所有資料后,我使用存盤程序將其插入 SQL 資料庫(這是不同的程序)。
所以我的問題是,如何加快獲取程序的速度,我考慮過并行運行任務,但我需要從回應中獲取結果。
現在這個程序看起來像這樣:
protected async void FSL_Sync_btn_Click(object sender, EventArgs e)
{
int offset = 0;
int total= 0;
bool isFirst = true;
DataTable resTbl = CreateDt();
while (offset < total || offset == 0)
{
try
{
var data = await GetFSLData(offset.ToString(),"Products");
JObject Jresult = JObject.Parse(data);
if (isFirst)
{
Int32.TryParse(Jresult.SelectToken("total").ToString(),out total);
isFirst = false;
}
// Function to chain up data in DataTable
resTbl = WriteInDataTable(resTbl, Jresult);
offset = 20;
}
catch(Exception ex)
{
var msg = ex.Message;
}
}
}
所以我采取的流程是:
- 從 API 獲取資料(假設前 20 條記錄)。
- Add it two existing
DataTableusingWriteInDataTablefunction. - Insert data into SQL Database from this
resTblDatatable(completely different process, not shown in this screenshot).
I haven't used parallel tasks yet (don't even know if it's a correct solution for it), so would appreciate any help.
uj5u.com熱心網友回復:
如果您已升級到 .NET 6 平臺,則可以考慮使用該Parallel.ForEachAsync方法來并行化GetFSLData呼叫。此方法需要一個IEnumerable<T>序列作為源。您可以使用 LINQ(Enumerable.Range方法)創建此序列。為避免與類的執行緒安全相關的任何問題DataTable,您可以將JObject結果存盤在中間ConcurrentQueue<JObject>集合中,并推遲創建 ,DataTable直到所有資料都已獲取并在本地可用。您可能還需要存盤offset與 each 相關聯的JObject,以便可以按原始順序插入結果。把所有東西放在一起:
protected async void FSL_Sync_btn_Click(object sender, EventArgs e)
{
int total = Int32.MaxValue;
IEnumerable<int> offsets = Enumerable
.Range(0, Int32.MaxValue)
.Select(n => checked(n * 20))
.TakeWhile(offset => offset < Volatile.Read(ref total));
var options = new ParallelOptions() { MaxDegreeOfParallelism = 10 };
var results = new ConcurrentQueue<(int Offset, JObject JResult)>();
await Parallel.ForEachAsync(offsets, options, async (offset, ct) =>
{
string data = await GetFSLData(offset.ToString(), "Products");
JObject Jresult = JObject.Parse(data);
if (offset == 0)
{
Volatile.Write(ref total,
Int32.Parse(Jresult.SelectToken("total").ToString()));
}
results.Enqueue((offset, Jresult));
});
DataTable resTbl = CreateDt();
foreach (var (offset, Jresult) in results.OrderBy(e => e.Offset))
{
resTbl = WriteInDataTable(resTbl, Jresult);
}
}
的Volatile.Read/Volatile.Write需要,因為total變數可能通過并行多執行緒訪問。
為了獲得最佳性能,您可能需要MaxDegreeOfParallelism根據遠程服務器的功能和 Internet 連接調整配置。
注意:此解決方案在記憶體方面效率不高,因為它要求所有資料同時以兩種不同的格式存盤在記憶體中。
uj5u.com熱心網友回復:
您可以使用Task.WhenAll并行運行您的請求。
public async Task<IEnumerable<string>> GetDataInParallel()
{
var tasks = new List<Task<string>>();
while(...)
{
var dataTask = GetFastLaneData(offset.ToString(), "Products"); // does not launch request, only add task to a list
tasks.Add(dataTask);
offset = 20
}
var datas = await Task.WhenAll(tasks); // launch all tasks
return datas;
}
此方法將嘗試創建或利用 1000 個執行緒并管理它們,這可能對性能有害,但比按順序啟動它們要快得多。您可能會考慮對它們進行批處理以實作更好的性能并一次啟動 100 個任務。
uj5u.com熱心網友回復:
由于代碼中的高抽象級別(恕我直言很好,但很難在像 SO 這樣的頁面上發現錯誤),因此很難知道您真正在使用和獲得什么。
因此,這里只是關于如何并行化對 API 的所有請求以縮短獲取時間并將結果一次寫入資料庫的草圖。也許 API 上有一些配額,您可能必須分塊運行這些東西,但這可以通過 LINQ 輕松采用。
var httpClient = new HttpClient();
var requests = Enumerable.Range(0, 1500)
.Where(i => i % 20 == 0)
// Create all needed requests
.Select(offset => $"https://www.someDummyAPI.com/Api/Products?offset={offset}&count=20")
.Select(url => new HttpRequestMessage(HttpMethod.Get, url))
// Create tasks to call these requests
.Select(request => httpClient.SendAsync(request));
// Run all of these requests in parallel.
var responses = await Task.WhenAll(requests);
// Create all tasks to get the content out of the requests
var allContentStreams = responses
.Select(response => response.Content.ReadAsStringAsync());
// Retrieve all content bodies as strings
var allRawContents = await Task.WhenAll(allContentStreams);
// Serialize strings into some usable object
var allData = allRawContents
.Select(JsonConvert.DeserializeObject<MyDataDTO>);
// Add all objects to the database context.
foreach (var data in allData)
{
WriteIntoDatabase(data);
}
// Let context persist data into database.
SaveDatabase();
uj5u.com熱心網友回復:
獲取您的第一條記錄并在回圈之前先設定總數:
var data = await GetFSLData(offset.ToString(),"Products");
JObject Jresult = JObject.Parse(data);
Int32.TryParse(Jresult.SelectToken("total").ToString(),out total);
在下一步中,您可以并行化您的任務:
DataTable resTbl = CreateDt();
var downloadTasks = new List<Task>();
while (offset < total)
{
downloadTasks.Add(GetFSLData(offset.ToString(),"Products"));
offset = 20;
}
然后就可以使用Task.WhenAll來獲取資料
var httpResults = await Task.WhenAll(downloadTasks);
foreach (var jObjectResult in httpResults.Select(JObject.Parse))
{
resTbl = WriteInDataTable(resTbl, Jresult);
}
需要注意的一些事情:您將同時使用大量請求訪問該 API,這可能不是一個好主意。如果遇到此問題,您可以在 TPL 資料流庫中使用TransformBlock和ActionBlock。你可以在這里找到更多資訊:
https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/360512.html
