在 C# 中从循环中异步调用方法
Posted
技术标签:
【中文标题】在 C# 中从循环中异步调用方法【英文标题】:To call method asynchronously from loop in c# 【发布时间】:2015-05-13 06:20:40 【问题描述】:我有一个要求,我从 Sailthru API 中提取数据。问题是,如果我同步进行调用,因为响应时间取决于数据,这将需要很长时间。我对线程有很多新的了解并尝试了一些东西,但它似乎没有按预期工作。任何人都可以请指导。 下面是我的示例代码
public void GetJobId()
Hashtable BlastIDs = getBlastIDs();
foreach (DictionaryEntry entry in BlastIDs)
Hashtable blastStats = new Hashtable();
blastStats.Add("stat", "blast");
blastStats.Add("blast_id", entry.Value.ToString());
//Function call 1
//Thread newThread = new Thread(() =>
//
GetBlastDetails(entry.Value.ToString());
//);
//newThread.Start();
public void GetBlastDetails(string blast_id)
Hashtable tbData = new Hashtable();
tbData.Add("job", "blast_query");
tbData.Add("blast_id", blast_id);
response = client.ApiPost("job", tbData);
object data = response.RawResponse;
JObject jtry = new JObject();
jtry = JObject.Parse(response.RawResponse.ToString());
if (jtry.SelectToken("job_id") != null)
//Function call 2
Thread newThread = new Thread(() =>
GetJobwiseDetail(jtry.SelectToken("job_id").ToString(), client,blast_id);
);
newThread.Start();
public void GetJobwiseDetail(string job_id, SailthruClient client,string blast_id)
Hashtable tbData = new Hashtable();
tbData.Add("job_id", job_id);
SailthruResponse response;
response = client.ApiGet("job", tbData);
JObject jtry = new JObject();
jtry = JObject.Parse(response.RawResponse.ToString());
string status = jtry.SelectToken("status").ToString();
if (status != "completed")
//Function call 3
Thread.Sleep(3000);
Thread newThread = new Thread(() =>
GetJobwiseDetail(job_id, client,blast_id);
);
newThread.Start();
string str = "test sleeping thread";
else
string export_url = jtry.SelectToken("export_url").ToString();
TraceService(export_url);
SaveCSVDataToDB(export_url,blast_id);
我希望 函数调用 1 异步启动(或者可能在 3 秒的间隙后启动以避免处理器负载)。在 函数调用 3 中,如果状态未完成并延迟 3 秒,我将再次调用相同的函数,以便有时间接收响应。
如果我的问题听起来很愚蠢,也请纠正我。
【问题讨论】:
【参考方案1】:你永远不应该像这样使用Thread.Sleep
,因为你不知道3000ms是否足够,你应该使用Task类而不是使用Thread类,这是更好的选择,因为它提供了一些额外的功能,线程池管理等等应该将您的函数结果保存到数据库中。如果您能够使用 .NET 4.5,您应该尝试使用 async/await 功能。
更简单的解决方案:
Parallel.ForEach
在互联网上获取一些有关它的详细信息,您不必对线程一无所知。它将在另一个线程上调用每个循环迭代。
【讨论】:
感谢您的建议,我会试一试【参考方案2】:首先,不惜一切代价避免在您的代码中开始新的Thread
s;这些线程会像一颗坍塌的星星一样吸走你的内存,因为每个线程都分配了大约 1MB 的内存。
现在是代码 - 根据框架,您可以从以下选项中进行选择:
ThreadPool.QueueUserWorkItem
用于旧版本的 .NET Framework
Parallel.ForEach
用于 .NET Framework 4
async
和 await
用于 .NET Framework 4.5
TPL Dataflow
也适用于 .NET Framework 4.5
您显示的代码非常适合 Dataflow,而且我不建议在此处使用 async/await
,因为它的使用会改变您的示例是一种 fire and forget
机制,这违反了使用 async/await
的建议。
要使用Dataflow
,您需要大体上:
TransformBlock
,它将一个字符串作为输入并从API返回响应
一个BroadcastBlock
会将响应广播到
两个ActionBlock
s;一个用于将数据存储在数据库中,另一个用于调用TraceService
代码应如下所示:
var downloader = new TransformBlock<string, SailthruResponse>(jobId =>
var data = new HashTable();
data.Add("job_id", jobId);
return client.ApiGet("job", data);
);
var broadcaster = new BroadcastBlock<SailthruResponse>(response => response);
var databaseWriter = new ActionBlock<SailthruResponse>(response =>
// save to database...
)
var tracer = new ActionBlock<SailthruResponse>(response =>
//TraceService() call
);
var options = new DataflowLinkOptions PropagateCompletion = true ;
// link blocks
downloader.LinkTo(broadcaster, options);
broadcaster.LinkTo(databaseWriter, options);
broadcaster.LinkTo(tracer, options);
// process values
foreach(var value in getBlastIds())
downloader.Post(value);
downloader.Complete();
【讨论】:
【参考方案3】:尝试如下异步。
async Task Task_MethodAsync()
// . . .
// The method has no return statement.
【讨论】:
以上是关于在 C# 中从循环中异步调用方法的主要内容,如果未能解决你的问题,请参考以下文章