C# 如何确保线程在完成特定任务后获得DIB

C# 如何确保线程在完成特定任务后获得DIB,c#,multithreading,thread-safety,signalr,race-condition,C#,Multithreading,Thread Safety,Signalr,Race Condition,关于多线程,我有点困惑。 我目前正在使用SinglaR开发一个实时服务。其思想是,一个连接的用户可以向另一个用户请求数据。 下面是请求和响应函数的概要 考虑以下代码: private readonly ConcurrentBag _sharedObejcts= new ConcurrentBag(); 请求: [...] var sharedObject = new MyObject(); _sharedObejcts.Add(sharedObject); ForwardRequestFi

关于多线程,我有点困惑。 我目前正在使用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;

[...]

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

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

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


有什么方法可以保证在调用
Task.Delay()
后第一个调用的是
TryTake()
调用?

您不希望生产者取消消费者的等待。这是太多的责任混淆

相反,您真正想要的是生产者发送一个异步信号。这是通过
TaskCompletionSource
完成的。使用者可以添加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;
类MyObject
{
public TaskCompletionSource ResponseProperty{get;}=new TaskCompletionSource();
}
//请求(消费者):
var sharedObject=新的MyObject();
_sharedObejcts.Add(sharedObject);
ForwardRequestFireAndForget();
var responseTask=sharedObject.ResponseProperty.Task;
if(等待任务时)(任务延迟(30000),响应任务)!=响应任务)
返回null;
_myConcurrentBag.TryTake(sharedObject);
返回等待响应任务;
//答复(制作人):
var结果=DoSomePossiblyVeryLengthyTaskHere();
var sharedObject=ConcurrentBag
.其中(x)
.FirstOrDefault();
//请求已超时,因此对象不再存在。
if(sharedObject==null)
回应;
sharedObject.ResponseProperty.TrySetResult(结果);
返回其他人的响应;

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

我不确定我是否正确理解了这个问题,但是如果您想在任务完成后调用某个东西,您可以使用ContinueWith@Fake但是在执行
ContinueWith(Task)
之前,其他线程是否有可能检索到共享对象,因此,找到一个不应该存在的对象?@AsPas-在尝试处理该对象之前,始终从
ConcurrentBag
中删除该对象。那么就没有竞争条件了。但是如果宿主仍然准时,那么它就没有办法访问该对象。@AsPas-哦,哇。我明白了。这是一种非常奇怪的方法。但是生产者如何知道消费者timedout或接受提供的结果?@AsPas:为什么生产者需要知道?因为根据结果是否被接受,消费者函数中应该返回不同的结果。不过,我已经阅读了有关TaskCompletionSource的文档。如果请求超时,我可以尝试从生产者端调用TrySetCancel。因此,如果任务已经处于取消、故障或RanToCompletion状态,则TrySetResult函数将返回false。但是,你的提示确实是一个不错的选择,因为它消除了任何竞争条件,使同步变得非常容易。非常感谢。