【问题标题】:Wait multiple threads for one result为一个结果等待多个线程
【发布时间】:2016-11-13 04:55:00
【问题描述】:

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

现在发生的情况是,在向 Web 服务请求例如 CustomerID = 12345 期间,另一个线程尝试为同一客户调用 Web 服务。现在我想腾出服务器来回收请求的结果。 因此,我想等待第二个线程,直到获得第一个请求的结果并将结果也返回给第二个线程。

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

谁能解释一下,我如何在 C# 中实现这一点。

【问题讨论】:

标签: c# multithreading


【解决方案1】:

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

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;
}

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

感谢这里的帖子给予的启发。

【讨论】:

    【解决方案2】:

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

    • 创建一个字典来存储动作和正在执行的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";
    }
    

    【讨论】:

    • 您的回答毫无用处,因为它指向了关键字的使用,而没有任何解释应该如何使用。它可能只是一个评论。
    • 您需要在finally 中添加tasks.Remove,以防task 导致异常。
    • @StephenCleary 确实如此。谢谢。
    【解决方案3】:

    你可以这样做:

    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请求,如果另一个线程想要访问同一个请求,你只需返回已经缓存的任务,如果它不存在,你就派生该任务并将其缓存以供将来的请求使用。

    【讨论】:

    • 您仍然可能会同时运行两个任务。如果两者同时通过if(!s_urlToContents.TryGetValue(url, out contents)),您将在两次执行中创建一个任务。
    猜你喜欢
    • 2011-07-10
    • 2020-10-04
    • 2011-07-21
    • 1970-01-01
    • 1970-01-01
    • 2019-07-23
    • 1970-01-01
    • 1970-01-01
    • 2017-08-14
    相关资源
    最近更新 更多