在 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】:

首先,不惜一切代价避免在您的代码中开始新的Threads;这些线程会像一颗坍塌的星星一样吸走你的内存,因为每个线程都分配了大约 1MB 的内存。

现在是代码 - 根据框架,您可以从以下选项中进行选择:

    ThreadPool.QueueUserWorkItem 用于旧版本的 .NET Framework Parallel.ForEach 用于 .NET Framework 4 asyncawait 用于 .NET Framework 4.5 TPL Dataflow 也适用于 .NET Framework 4.5

您显示的代码非常适合 Dataflow,而且我不建议在此处使用 async/await,因为它的使用会改变您的示例是一种 fire and forget 机制,这违反了使用 async/await 的建议。

要使用Dataflow,您需要大体上:

一个TransformBlock,它将一个字符串作为输入并从API返回响应 一个BroadcastBlock 会将响应广播到 两个ActionBlocks;一个用于将数据存储在数据库中,另一个用于调用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# 中从循环中异步调用方法的主要内容,如果未能解决你的问题,请参考以下文章

如何从 getter 或 setter 调用异步方法?

C# 异步方法加await调用不就变成同步方法了吗

如何正确调用 Parallel.ForEach 循环中的调用异步方法[重复]

在 C# 中的类构造函数中调用异步方法 [重复]

C#异步调用四大方法详解

C# 中有没有办法在不使调用者也异步的情况下调用异步方法? [复制]