【问题标题】:How can I make sure a thread gets dibs after a certain Task如何确保线程在某个任务后获得 dibs
【发布时间】:2021-03-31 16:12:27
【问题描述】:

我对多线程有点困惑。 我目前正在使用 SinglaR 开发实时服务。这个想法是连接的用户可以向另一个用户请求数据。 下面是请求和响应函数的概要。

考虑以下代码:

private readonly ConcurrentBag _sharedObejcts= new ConcurrentBag();

请求:

[...]

var sharedObject = new MyObject();
_sharedObejcts.Add(sharedObject);

ForwardRequestFireAndForget();

try
{
    await Task.Delay(30000, sharedObject.myCancellationToken);
}
catch
{
    return sharedObject.ResponseProperty;
}

_myConcurrentBag.TryTake(sharedObject);

[...]

回应:

[...]

var result = DoSomePossiblyVeryLengthyTaskHere();

var sharedObject = ConcurrentBag 
    .Where(x)
    .FirstOrDefault();

// The request has timed out so the object isn't there anymore.
if(sharedObject == null)
{
    return someResponse;
}

sharedObject.ResponseProperty = result;

// triggers the cancellation source
sharedObject.Cancel();

return someOtherResponse;

[...]

所以基本上是向服务器发出请求,然后转发到另一台主机,然后函数等待取消或超时。

其他主机调用response函数,添加repsonseObject并触发myCancellationToken

但是我不确定这是否代表竞争条件。 理论上,响应线程可以检索sharedObject,而另一个线程仍然位于 finally 块上吗? 这意味着,请求已经超时,任务还没有从包中取出对象,这意味着数据不一致。

有什么方法可以确保在Task.Delay() 调用之后首先调用的是TryTake()call?

【问题讨论】:

  • 我不确定我是否正确理解了这个问题,但如果你想在任务完成后调用某些东西,你可以使用 ContinueWith
  • @Fake 但在执行ContinueWith(Task) 之前是否有其他线程检索共享对象,从而找到一个不应该存在的对象?
  • @AsPas - 在尝试处理对象之前,请始终从ConcurrentBag 中删除对象。那么就没有竞争条件了。
  • 但是如果他仍然准时,主机就没有办法访问该对象。
  • @AsPas - 哦,哇。我知道了。这是一种非常奇怪的做法。

标签: c# multithreading thread-safety signalr race-condition


【解决方案1】:

您不想让生产者取消消费者的等待。太多的职责混为一谈了。

相反,您真正想要的是生产者发送异步信号。这是通过TaskCompletionSource<T> 完成的。消费者可以使用不完整的 TCS 添加对象,然后消费者可以(异步)等待该 TCS 完成(或超时)。然后生产者将其价值赋予 TCS。

类似这样的:

class MyObject
{
  public TaskCompletionSource<MyProperty> ResponseProperty { get; } = new TaskCompletionSource<MyProperty>();
}


// request (consumer):

var sharedObject = new MyObject();
_sharedObejcts.Add(sharedObject);

ForwardRequestFireAndForget();

var responseTask = sharedObject.ResponseProperty.Task;
if (await Task.WhenAny(Task.Delay(30000), responseTask) != responseTask)
  return null;

_myConcurrentBag.TryTake(sharedObject);
return await responseTask;


// response (producer):

var result = DoSomePossiblyVeryLengthyTaskHere();
var sharedObject = ConcurrentBag 
    .Where(x)
    .FirstOrDefault();

// The request has timed out so the object isn't there anymore.
if(sharedObject == null)
  return someResponse;

sharedObject.ResponseProperty.TrySetResult(result);
return someOtherResponse;

上面的代码可以稍微清理一下;具体来说,让生产者拥有共享对象的“生产者视图”和消费者拥有“消费者视图”,这两个接口都由相同的类型实现,这不是一个坏主意。但是上面的代码应该会给你大致的想法。

【讨论】:

  • 但是生产者如何知道消费者是否超时或接受了提供的结果?
  • @AsPas:为什么制作人需要知道?
  • 因为根据结果是否被接受,消费者函数中应该返回不同的结果。不过,我已经阅读了有关 TaskCompletionSource 的文档。如果请求超时,我可以尝试从生产者端调用 TrySetCancel。因此,如果任务已经处于取消、故障或 RanToCompletion 状态,TrySetResult 函数将返回 false。但是,是的,您的提示肯定是要走的路,因为它消除了任何竞争条件并使同步变得非常容易。谢谢。
猜你喜欢
  • 2016-09-08
  • 2023-04-02
  • 1970-01-01
  • 1970-01-01
  • 2020-04-06
  • 1970-01-01
  • 2017-03-08
  • 2021-06-01
  • 2020-09-26
相关资源
最近更新 更多