我想出了这个解决方案。 (尚未测试)通过网络上的大量弹跳。
Private Function ObserveUDP() As IObservable(Of bytes())
Dim f = Function(observer)
Dim endpoint = New IPEndPoint(IPAddress.Parse(Me.IpAdress), Me.IPPort)
Dim client = New UdpClient(endpoint)
Dim obs = observable.*emphasized text*Generate(Of Task(Of UdpReceiveResult), UdpReceiveResult) _
( Nothing _
, Function(task As Task(Of UdpReceiveResult)) task Is Nothing Or Not task.IsCompleted() _
, Function(task As Task(Of UdpReceiveResult)) client.ReceiveAsync() _
, Function(task As Task(Of UdpReceiveResult)) task.Result)
Dim observable = obs.Select(Function(r) r.Buffer)
dim handle = observable.Subscribe(observer)
Dim df = Sub()
client.Close()
handle.Dispose()
End Sub
Return Disposable.Create(df)
End Function
Return observable.Create(f)
End Function
我的要求是确保在订阅被删除时关闭UDP客户端。我很确定上面的代码很接近,但我认为这不太对。任何意见都将不胜感激。
*编辑*
实际上上面的例子是完全错误的,只会同步创建大量的任务对象 但不等待他们。经过一些试验和错误后,我想出了以下通用函数 展开一个一次又一次地叫做的等待。有什么意见吗?
''' initializer - a function that initializes and returns the state object
''' generator - a function that asynchronously using await generates each value
''' finalizer - a function for cleaning up the state object when the sequence is unsubscribed
Private Function ObservableAsyncSeq(Of T, I)( _
initializer As Func(Of I), _
generator As Func(Of I, Task(Of T)), _
finalizer As Action(Of I)) As IObservable(Of T)
Dim q = Function(observer As IObserver(Of T))
Dim go = True
Try
Dim r = Async Sub()
Dim ii As I = initializer()
While go
Dim result = Await generator(ii)
observer.OnNext(result)
End While
finalizer(ii)
observer.OnCompleted()
End Sub
Task.Run(r)
Catch ex As Exception
observer.OnError(ex)
End Try
' Disposable for stopping the sequence as per
' the observable contract
Return Sub() go = False
End Function
Return Observable.Create(q)
End Function
与UDP一起使用的例子
Private Function ObserveMeasurementPoints2() As IObservable(Of ProcessedDate)
Dim initializer = Function()
Dim endpoint = New IPEndPoint(IPAddress.Parse(Me.IpAdress), Me.IPPort)
Return New UdpClient(endpoint)
End Function
Dim finalizer = Function(client As UdpClient)
client.Close()
End Function
Dim generator = Function(client As UdpClient) As Task(Of UdpReceiveResult)
Return client.ReceiveAsync()
End Function
Return ObservableAsyncSeq(initializer, generator, finalizer).Select(Function(r) ProcessBytes(r.Buffer))
End Function
答案 0 :(得分:4)
您可以使用Observable.Using
作为提及的Enigmativity,或者只使用接受Observable.Create
作为返回参数的常规IDisposable
方法 - 这足以安全处置。
使用迭代器或异步非常好。我已经列出了更多Rx-ish方法:
Public Shared Function UdpStream(Of T)(endpoint As IPEndPoint, processor As Func(Of Byte(), T)) As IObservable(Of T)
Return Observable.Using(Of T, UdpClient)(
Function() New UdpClient(endpoint),
Function(udpClient) _
Observable.Defer(Function() udpClient.ReceiveAsync().ToObservable()) _
.Repeat() _
.Select(Function(result) processor(result.Buffer))
)
End Function
传统方式:
Public Shared Function UdpStream(Of T)(endpoint As IPEndPoint, processor As Func(Of Byte(), T)) As IObservable(Of T)
Return Observable.Using(
Function() New UdpClient(endpoint),
Function(udpClient) Observable.Defer( _
Observable.FromAsyncPattern(
AddressOf udpClient.BeginReceive,
Function(iar)
Dim remoteEp = TryCast(iar.AsyncState, IPEndPoint)
Return udpClient.EndReceive(iar, remoteEp)
End Function)
).Repeat() _
.Select(processor)
)
End Function
测试:
Shared Sub Main()
Using UdpStream(New IPEndPoint(IPAddress.Loopback, 13200),
Function(bytes) String.Join(",", bytes)
).Subscribe(AddressOf Console.WriteLine)
Console.ReadLine()
End Using
Console.WriteLine("Done")
Console.ReadKey()
End Sub
答案 1 :(得分:2)
查看Observable.Using
- 它专门用于创建一个使用一次性资源生成其值的observable,完成后会自动处理资源。
您会发现UdpClient
具有相同的Close
& Dispose
方法实施,因此如果您致电Close
,则无需致电Dispose
。
来自反射器:
void IDisposable.Dispose()
{
this.Dispose(true);
}
public void Close()
{
this.Dispose(true);
}
以下是Using
的签名:
Public Shared Function Using(Of TSource, TResource As IDisposable)(
ByVal resourceFactory As Func(Of TResource),
ByVal observableFactory As Func(Of TResource, IObservable(Of TSource)))
As IObservable(Of TSource)
答案 2 :(得分:1)
之前我没有使用过UDPClient,但看起来您正在使用Tasks(Cardinality = 1)来尝试接收数据流(Cardinality = many)。似乎解决了这个问题,你已经对你的查询重复了一遍。这意味着您的查询将执行此操作
我认为您应该能够通过拉入字节流来读取套接字/网络连接。我在博客文章中向您展示了如何做到这一点:
http://introtorx.com/Content/v1.0.10621.0/15_SchedulingAndThreading.html#CreatingYourOwnIterator
通过这种方式,您只需打开一个连接,并在收到字节时按下它们。
快速谷歌我也发现有人担心UDPClient实施的可靠性。 http://www.codeproject.com/Articles/1938/Issues-with-UdpClient-Receive
HTH
利
using System;
using System.IO;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
namespace MyLib
{
public static class ObservableExtensions
{
//TODO: Could potentially upgrade to using tasks/Await-LC
public static IObservable<byte> ToObservable(
this Stream source,
int buffersize,
IScheduler scheduler)
{
var bytes = Observable.Create<byte>(o =>
{
var initialState = new StreamReaderState(source, buffersize);
var currentStateSubscription = new SerialDisposable();
Action<StreamReaderState, Action<StreamReaderState>> iterator =
(state, self) =>
currentStateSubscription.Disposable = state.ReadNext()
.Subscribe(
bytesRead =>
{
for (int i = 0; i < bytesRead; i++)
{
o.OnNext(state.Buffer[i]);
}
if (bytesRead > 0)
self(state);
else
o.OnCompleted();
},
o.OnError);
var scheduledWork = scheduler.Schedule(initialState, iterator);
return new CompositeDisposable(currentStateSubscription, scheduledWork);
});
return Observable.Using(() => source, _ => bytes);
}
private sealed class StreamReaderState
{
private readonly int _bufferSize;
private readonly Func<byte[], int, int, IObservable<int>> _factory;
public StreamReaderState(Stream source, int bufferSize)
{
_bufferSize = bufferSize;
_factory = Observable.FromAsyncPattern<byte[], int, int, int>(
source.BeginRead,
source.EndRead);
Buffer = new byte[bufferSize];
}
public IObservable<int> ReadNext()
{
return _factory(Buffer, 0, _bufferSize);
}
public byte[] Buffer { get; set; }
}
}
}