Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/265.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用Rx在异步操作上阻塞(可能超时)_C#_.net_System.reactive - Fatal编程技术网

C# 使用Rx在异步操作上阻塞(可能超时)

C# 使用Rx在异步操作上阻塞(可能超时),c#,.net,system.reactive,C#,.net,System.reactive,我正在尝试使用.NET的反应式扩展重写一些代码,但我需要一些关于如何实现我的目标的指导 我有一个类,它将一些异步行为封装在一个低级库中。想一想读或写网络的东西。当类启动时,它将尝试连接到环境,当成功时,它将通过从工作线程调用来发回信号 我想将这种异步行为转化为同步调用,并在下面创建了一个大大简化的示例,说明如何实现这一点: ManualResetEvent readyEvent = new ManualResetEvent(false); public void Start(TimeSpan

我正在尝试使用.NET的反应式扩展重写一些代码,但我需要一些关于如何实现我的目标的指导

我有一个类,它将一些异步行为封装在一个低级库中。想一想读或写网络的东西。当类启动时,它将尝试连接到环境,当成功时,它将通过从工作线程调用来发回信号

我想将这种异步行为转化为同步调用,并在下面创建了一个大大简化的示例,说明如何实现这一点:

ManualResetEvent readyEvent = new ManualResetEvent(false);

public void Start(TimeSpan timeout) {
  // Simulate a background process
  ThreadPool.QueueUserWorkItem(_ => AsyncStart(TimeSpan.FromSeconds(1)));
  // Wait for startup to complete.
  if (!this.readyEvent.WaitOne(timeout))
    throw new TimeoutException();
}

void AsyncStart(TimeSpan delay) {
  Thread.Sleep(delay); // Simulate startup delay.
  this.readyEvent.Set();
}
在工作线程上运行
AsyncStart
,只是模拟库的异步行为的一种方式,而不是我真正的代码的一部分,低级库提供线程并在回调时调用我的代码

请注意,如果启动未在超时间隔内完成,则
Start
方法将抛出
TimeoutException

我想重写此代码以使用Rx。这是我的第一次尝试:

Subject<Unit> readySubject = new Subject<Unit>();

public void Start(TimeSpan timeout) {
  ThreadPool.QueueUserWorkItem(_ => AsyncStart(TimeSpan.FromSeconds(1)));
  // Point A - see below
  this.readySubject.Timeout(timeout).First();
}

void AsyncStart(TimeSpan delay) {
  Thread.Sleep(delay);
  this.readySubject.OnNext(new Unit());
}
现在,异步操作不会立即启动,而是仅在使用
IObservable
时启动。不幸的是,仍然存在争用条件,但这次是在B点。如果异步操作在
延迟
lambda返回之前启动调用
OnNext
,它仍然丢失,并且
超时
将引发
TimeoutException

我知道我可以使用像
Replay
这样的操作符来缓冲事件,但是我最初的例子没有使用Rx,没有使用任何类型的缓冲。有没有一种方法可以让我在没有比赛条件的情况下使用Rx来解决我的问题?本质上,只有在
IObservable
连接到后才能启动异步操作。在这种情况下,是
超时
第一次


根据Paul Betts的回答,这里是可行的解决方案:

void Start(TimeSpan timeout) {
  var readySubject = new AsyncSubject<Unit>();
  ThreadPool.QueueUserWorkItem(_ => AsyncStart(readySubject, TimeSpan.FromSeconds(1)));
  // Point C - see below
  readySubject.Timeout(timeout).First();
}

void AsyncStart(ISubject<Unit> readySubject, TimeSpan delay) {
  Thread.Sleep(delay);
  readySubject.OnNext(new Unit());
  readySubject.OnCompleted();
}
void开始(TimeSpan超时){
var readySubject=new AsyncSubject();
ThreadPool.QueueUserWorkItem(=>AsyncStart(readySubject,TimeSpan.FromSeconds(1));
//C点——见下文
Timeout(Timeout.First();
}
void AsyncStart(ISubject readySubject,TimeSpan delay){
睡眠(延迟);
OnNext(新单元());
readySubject.OnCompleted();
}

有趣的是,C点的延迟超过了
AsyncStart
完成所需的时间
AsyncSubject
保留上次发送的通知,
Timeout
First
仍将按预期执行。

因此,关于Rx,我想很多人(包括我自己!)一开始都会做一件事:如果您使用任何传统的线程功能,如ResetEvents、Thread.Sleeps或其他,你做错了(tm)-这就像在LINQ中对数组进行强制转换一样,因为你知道底层类型恰好是数组

要知道的关键是,异步func由一个返回
IObservable
的函数表示——这是一种神奇的调味品,可以让您在某个操作完成时发出信号。下面是如何“Rx ify”一个更传统的异步函数,就像您在Silverlight web服务中看到的那样:

IObservable<byte[]> readFromNetwork()
{
    var ret = new AsyncSubject();
    // Here's a traditional async function that you provide a callback to
    asyncReaderFunc(theFile, buffer => {
        ret.OnNext(buffer);
        ret.OnCompleted();
    });

    return ret;
}

我想进一步补充Paul的评论,添加WaitHandles意味着你做错了,直接使用主题通常意味着你也做错了

<>试着考虑你的RX代码与序列或流水线一起工作。主题提供读写功能,这意味着你不再使用管道或序列(除非你有双向的管道或可以反转的序列?!)

首先,保罗的代码很酷,但让我们“滚开”

1st异步启动方法将其更改为

IObservable<Unit> AsyncStart(TimeSpan delay) 
{
  Observable.Timer(delay).Select(_=>Unit.Default);
}
现在我们有了一个清晰的管道或事件序列

异步启动-->isReady-->启动

而不是开始-->异步开始-->开始


如果我对您的问题空间了解得更多,我相信我们可以想出一种更好的方法来解决这个问题,而不需要start方法的阻塞特性。你使用Rx的次数越多,你就会发现你对何时需要阻止、使用waithandles等的旧假设会被扔出窗外。

谢谢你的回答。这个问题是在Rx发布之前发布的,从那时起API表面发生了很大变化。关于我的特殊问题,恐怕我不能把障碍物扔出窗外。我使用的API需要多阶段初始化,但是使用Rx允许我并行化其中的一些。我发现Rx对于多阶段初始化也非常有用,但是我也发现我仍然不需要阻塞。通常,您可以在所有部件就绪时引发一个事件,指示系统就绪。然后,您可以有效地进行选择(多个),以调用在调用之前会阻止的内容。哦,ps,我提供的代码将在过去3年中在所有Rx API上运行(在某些变体中)。如果您必须在执行D之前完成a、B和C,并且并行运行a、B和C,则必须在完成a时进行阻止,在执行D之前,B和C。Rx不能改变这一点。但是,这样做可以帮助您摆脱复杂的状态处理。当A+B+C完成时,您可以触发D的开始。具体地说,我的观点是,执行continuations不需要.First()或任何其他实际阻止线程的操作符。这是一种常见的误解,很容易导致代码出现问题。
// Make it into a sync function
byte[] results = readFromNetwork().First();

// Keep reading blocks one at a time until we run out
readFromNetwork().Repeat().TakeUntil(x => x == null || x.Length == 0).Subscribe(bytes => {
    Console.WriteLine("Read {0} bytes in chunk", bytes.Length);
})

// Read the entire stream and get notified when the whole deal is finished
readFromNetwork()
    .Repeat().TakeUntil(x => x == null || x.Length == 0)
    .Aggregate(new MemoryStream(), (ms, bytes) => ms.Write(bytes))
    .Subscribe(ms => {
        Console.WriteLine("Got {0} bytes in total", ms.ToArray().Length);
    });

// Or just get the entire thing as a MemoryStream and wait for it
var memoryStream = readFromNetwork()
    .Repeat().TakeUntil(x => x == null || x.Length == 0)
    .Aggregate(new MemoryStream(), (ms, bytes) => ms.Write(bytes))
    .First();
IObservable<Unit> AsyncStart(TimeSpan delay) 
{
  Observable.Timer(delay).Select(_=>Unit.Default);
}
void Start(TimeSpan timeout) 
{
    var isReady = AsyncStart(TimeSpan.FromSeconds(1))
                    .SubscribeOn(Scheduler.ThreadPool)
                    .PublishLast();
    isReady.Connect();
    isReady.Timeout(timeout).First();
}