等待多个线程获得一个结果

时间:2016-07-11 09:35:34

标签: c# multithreading

我有一个网络服务,可以获取标识符的数据(例如客户ID的结算信息)。在我的客户端有多个线程。这些线程调用Web服务上的方法来获取标识符的数据。

现在它发生了,在请求Web服务时,例如=SUM(Fields!Amount.Value)另一个线程试图为同一个客户调用Web服务。现在我想节省服务器和回收请求的结果。 因此,我想等待第二个线程,直到得到第一个请求的结果,并将结果返回给第二个线程。

一个重要的要求是,如果第二个线程想要在Web请求返回后获取数据,则必须发送新请求。为了说明我的问题,我展示了sequence diagram

任何人都可以解释我,我怎样才能在C#中实现这一点。

3 个答案:

答案 0 :(得分:1)

您可以这样做:

private static ConcurrentDictionary<string, Task<string>> s_urlToContents;

public static Task<string> GetContentsAsync(string url)
{
    Task<string> contents;
    if(!s_urlToContents.TryGetValue(url, out contents))
    {
        contents = GetContentsAsync(url);
        contents.ContinueWith(t => s_urlToContents.TryAdd(url, t); },
        TaskContinuationOptions.OnlyOnRanToCompletion |
        TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
    }
    return contents;
}

private static async Task<string> GetContentsAsync(string url)
{
    var response = await new HttpClient().GetAsync(url);
    return response.EnsureSuccessStatusCode().Content.ReadAsString();
}

此代码缓存一个http请求,如果另一个线程想要访问同一个请求,则只返回已经缓存的任务,如果它不存在,则生成任务并将其缓存以备将来请求。

答案 1 :(得分:0)

我认为你可以这样做(伪代码):

  • 创建一个字典来存储操作和执行Task;
  • 收到请求时,暂时锁定字典,这样您就不会同时获得两个任务,创建任务并将其添加到字典中;
  • await任务(或线程);

  • 如果有其他请求,请首先检查字典中是否有正在运行的任务。如果它在那里,也等待它。如果没有,请创建一个新的。

这样的事情:

Dictionary<string, Task<string>> tasks = new Dictionary<string, Task<string>>();

public async Task<string> GetCustomer(int id)
{
    string taskName = $"get-customer-{id}";

    try
    {
        Task<string> task;

        lock (tasks)
        {
            if (!tasks.TryGetValue(taskName, out task))
            {
                task = new Task<string>(() => GetCustomerInternal(id));
                tasks[taskName] = task;
            }
        }

        string result = await task;

        return result;
    }
    finally
    {
        lock (tasks)
        {
            tasks.Remove(taskName);
        }
    }
}

private string GetCustomerInternal(int id)
{
    return "hello";
}

答案 2 :(得分:0)

最后,我结束了以下解决方案:

private class RequestHandler
{
    private EventWaitHandle _handle;

    public RequestHandler(Func<string> request)
    {
        _handle = new EventWaitHandle(false, EventResetMode.ManualReset);
        Start(request);
    }

    private void Start(Func<string> doRequest)
    {
        Result = doRequest();
        _handle.Set();
    }

    public void WaitForResponse()
    {
        _handle.WaitOne();
    }

    public string Result { get; private set; }
}

private Object _lockObject = new Object();
private Dictionary<string, RequestHandler> _pendingRequests = new Dictionary<string, RequestHandler>();

public string GetCustomer(int id)
{
    RequestHandler request;

    lock (_lockObject)
    {
        if (!_pendingRequests.TryGetValue(id, out request))
        {
            request = new RequestHandler(() => LoadEntity(id));
            _pendingRequests[id] = request;
        }
    }

    request.WaitForResponse();
    lock (_lockObject)
    {
        _pendingRequests.Clear();
    }
    return request.Result;
}

尽管仍然可以将重复的帖子发送到服务器,但这是一个非常小的机会(在第一个线程创建请求之后和第二个线程尝试获取相同的客户数据之前,线程会删除所有待处理的请求)。

感谢这里的帖子给予了灵感。