【问题标题】:Wait For Asynchronous Events to Finish in Foreach在 Foreach 中等待异步事件完成
【发布时间】:2015-04-21 01:58:42
【问题描述】:

如何使用 foreach 向服务器发送请求,当我收到响应时,foreach 等待返回的信息处理完毕,然后继续处理下一个请求。

我的问题:在 foreach 中我发送了许多请求,而 foreach 继续没有处理我得到响应的信息。

例如:

foreach (DataLoader.GetInfo items in listBoxFetInfo.Items)
{  
    DownloadInfo(items.CompanyName);                     
}

void DownloadInfo(string name)
{
   int requestId = feed.SendCompanyNameRequest(symbol.ToUpper(), sendType, sendTimePeriod, sendDateFrom, DateTime.Now);
}

feed.RequestCompanyName += new IFeedEvents_RequestCompanyNameEventHandler(feed_RequestName);

void feed_RequestName(int originalRequestId, short ResponseCode, string symbol, short symbolStatusCode, object records)
{
//save to file
}

我不能使用Start Multiple Async Tasks and Process Them As They Complete ,因为这个解决方案需要在我无法添加的地方添加 CancellationToken(不能在 void feed_RequestName 中)

这个问题还有什么解决办法?

另外,我无法更改以下签名:

feed_RequestName(int originalRequestId, short ResponseCode, string symbol, short symbolStatusCode, object records)

feed.SendCompanyNameRequest(symbol.ToUpper(), sendType, sendTimePeriod, sendDateFrom, DateTime.Now);

【问题讨论】:

  • 我认为我们可能需要更多信息。 DataLoader 是什么? feed 是什么?它们是你写的东西还是一些图书馆? SendCompanyNameRequest 是在另一个线程中执行还是阻塞调用?
  • @LosFrijoles feed 是库,dataLoader 是我创建列表时的构造函数,它将加载列表中项目的信息

标签: c#


【解决方案1】:

我正在摸索你的东西是如何工作的,但这是我会做的。由于您无法修改签名,因此您无法使用 Task,就像您建议的 last question 一样。

我在这里看到的是,您有一个开始请求的方法,然后是在请求完成时调用的事件。我假设您的请求在单独的线程上运行,或者在 feed 所在的库中运行。您需要做的是等待该事件完成一次,然后再继续下一个项目。如果这不是您的问题或您的图书馆是如何运作的,请告诉我。

如果您并行执行请求(意味着一次只执行一个请求并且没有其他人在使用您的feed 对象),您可以使用System.Threading.ManualResetEvent 类您的一种方法非常容易依赖于另一种方法。您可以执行以下操作:

  1. 在您的DownloadInfo 方法中,ResetManualResetEvent 这样无论何时有人调用WaitOne,它都会“阻塞”线程并阻止它继续运行。它将等到其他人在ManualResetEvent 上调用Set

  2. 调用获取requestId 的方法后,在ManualResetEvent 上调用WaitOne。这将等到“其他人”在ManualResetEvent 上调用Set

  3. 在您的 feed_RequestName 方法中,Set 完成处理后的手动重置事件。这将使您在步骤 2 中调用的WaitOne 停止阻塞并继续。如果您在调用 Set 之前将请求的结果存储在这些事物所在的对象上的某些变量中,则可以在 WaitOne 之后通过您的 DownloadInfo 方法访问结果。

一个简短的、不完整的例子:

class YourClassWhereYouPutTheseThings
{
    private ManualResetEvent _mre = new ManualResetEvent(true);

    //...your other stuff here...

    public YourClassWhereYouPutTheseThings()
    {
        //...your existing stuff
        feed.RequestCompanyName += new IFeedEvents_RequestCompanyNameEventHandler(feed_RequestName);
        //...your existing stuff
    }

    void YourMethod()
    {
        foreach (DataLoader.GetInfo items in listBoxFetInfo.Items)
        {
            DownloadInfo(items.CompanyName);                     
        }
    }

    void DownloadInfo(string name)
    {
        this._mre.Reset(); //make everyone listening to _mre wait until Set is called
        int requestId = feed.SendCompanyNameRequest(symbol.ToUpper(), sendType, sendTimePeriod, sendDateFrom, DateTime.Now);
        this._mre.WaitOne(); //wait for the feed_RequestName method to be called once
    }

    void feed_RequestName(int originalRequestId, short ResponseCode, string symbol, short symbolStatusCode, object records)
    {
       //save to file

       //store your information on the object somewhere if you would like

       //after saving to your file, set the manualresetevent so that the WaitOne() above can proceed
       this._mre.Set();
    }
}

重要提示:如果您的feed 被多个可以同时执行的对象共享,您需要确保您不会将其他对象的请求作为你的DownloadInfo 方法。任何请求都会导致您的feed_RequestName 方法被调用,因此您需要确定该方法的哪个调用对应于您的请求。以下是关于如何使这项工作发挥作用的一项建议:

  1. 将您在DownloadInfo 中获得的requestId 存储在feed_RequestName 可以访问的某个位置。如果在您的调用方法有机会执行之前调用了feed_RequestName,这可能会出现问题。您需要想办法解决这个问题。
  2. feed_RequestName 中,与存储的请求ID 进行比较,如果匹配,则仅在ManualResetEvent 上调用Set

另一个注意事项:您的库适用于并行执行请求。在进行下一个请求之前等待请求完成是非常低效的。你真正应该做的是(伪代码):

DownloadItems:
    Lock the object so that only one person uses the method at a time
    foreach item in requests
        call your start request method and get the id
        put the id into a thread-safe set stored on the object
    wait for the set to become empty

feed_RequestName:
    Do your thing that you've been doing
    Remove the id from the thread safe set

【讨论】:

  • 当我点击开始时,我的程序没有响应,可能是因为_mre.Reset?
  • 在主类中我创建了 void: public void SetMre(){_mre.Set();} 并在另一个类中的 feed_RequestName 中使用它。是真的吗?
  • 按照你的方式编写和工作,非常感谢,但有一些功能:当我启动程序时,我的程序没有响应。我可以从另一个线程开始吗?例如在 foreach 我有: this.Dispatcher.BeginInvoke(new ThreadStart(delegate { ProgressDataLoad.Value++; }));这不起作用
  • 主要问题是:我发送了大约 1000 个请求,但可以发送大约 250 个请求,现在这个问题 - 不是问题,因为工作)
  • 提供feed 的库是什么?我在这个答案中展示的方法只有在该库启动另一个线程来执行请求时才有效。猜测该库正在启动线程或在线程池上执行作业。您需要使用调试器并单步执行以查看您的 feed_RequestName 方法是否被调用并找出它何时被调用。
【解决方案2】:

你能用while代替foreach吗?

喜欢:

while(not done processing all requests) {
  //grab and process next request
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-11-13
    • 2020-10-04
    • 2019-05-31
    • 1970-01-01
    相关资源
    最近更新 更多