我有一些软件带有基于事件的控制网络协议,它使用IObservable<Event>
来处理绑定的消息。
在许多情况下,发送的消息将指示特定的响应(或序列,例如报告进度)。为了不会错过响应,正在使用FirstAsync
和ToTask
预先设置任务,但是如果任务永远不会完成,这些似乎会泄漏。
也不允许简单地将evtTask
放在using
块中,因为不允许处理不完整的任务。
var jobUuid = Guid.NewGuid();
var evtTask = Events.FirstAsync((x) => x.Action == Action.JobComplete && x.JobUuid == jobUuid).ToTask();
// e.g. if this throws without ever sending the message
await SendMessage($"job {jobUuid} download {url}");
var evt = await evtTask;
if (evt.Success)
{
...
}
图书馆是否为此用例提供了一种简单的方法,可以在退出范围时取消订阅?
var jobUuid = Guid.NewGuid();
using(var evtTask = Events.FirstAsync((x) => x.Action == Action.JobComplete && x.JobUuid == jobUuid)
.ToDisposableTask())) // Some method like this
{
// e.g. if this throws without ever sending the message
await SendMessage($"job {jobUuid} download {url}");
var evt = await evtTask;
if (evt.Success)
{
...
}
} // Get rid of the FirstAsync task if leave here before it completes for any reason
答案 0 :(得分:2)
处置Task
无济于事,因为它没有任何用处(在大多数情况下,包括这个)。什么会帮助取消任务。取消处置由ToTask
创建的基础订阅等,解决了这个&#34;泄漏&#34;。
所以它可以这样:
Task<Event> evtTask;
using (var cts = new CancellationTokenSource()) {
evtTask = Events.FirstAsync((x) => x.Action == Action.JobComplete && x.JobUuid == jobUuid)
.ToTask(cts.Token);
// e.g. if this throws without ever sending the message
try {
await SendMessage($"job {jobUuid} download {url}");
}
catch {
cts.Cancel(); // disposes subscription
throw;
}
}
var evt = await evtTask;
if (evt.Success)
{
...
}
当然你可以用一些更方便的形式(比如扩展方法)来包装它。例如:
public static class ObservableExtensions {
public static CancellableTaskWrapper<T> ToCancellableTask<T>(this IObservable<T> source) {
return new CancellableTaskWrapper<T>(source);
}
public class CancellableTaskWrapper<T> : IDisposable
{
private readonly CancellationTokenSource _cts;
public CancellableTaskWrapper(IObservable<T> source)
{
_cts = new CancellationTokenSource();
Task = source.ToTask(_cts.Token);
}
public Task<T> Task { get; }
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
}
}
然后它变得接近你想要的东西:
var jobUuid = Guid.NewGuid();
using (var evtTask = Events.FirstAsync((x) => x.Action == Action.JobComplete && x.JobUuid == jobUuid).ToCancellableTask()) {
await SendMessage($"job {jobUuid} download {url}");
var evt = await evtTask.Task;
if (evt.Success) {
...
}
}
答案 1 :(得分:1)
您可以使用TPL Timeout(由@Fabjan引用)或Rx / System.Reactive版本的Timeout。
using
听起来不错,但没有意义。使用相当于在使用块末尾的某些内容上调用.Dispose
。我假设这里的问题是你的代码永远不会超过await evtTask
。在假设的using
中抛出所有内容不会改变任何内容:您的代码仍在等待。
在更高级别,您的代码比被动代码更重要,您可能希望将其重构为:
var subscription = Events
.Where(x => x.Action == Action.JobComplete)
.Subscribe(x =>
{
if(x.Success)
{
//...
}
else
{
//...
}
});