C# 将TaskCompletionSource替换为Observable
在我的.NET4.0库中,我有一段代码通过网络发送数据并等待响应。为了不阻止调用代码,该方法返回一个C# 将TaskCompletionSource替换为Observable,c#,system.reactive,taskcompletionsource,C#,System.reactive,Taskcompletionsource,在我的.NET4.0库中,我有一段代码通过网络发送数据并等待响应。为了不阻止调用代码,该方法返回一个任务,该任务在收到响应时完成,以便代码可以像这样调用该方法: // Send the 'message' to the given 'endpoint' and then wait for the response Task<IResult> task = sender.SendMessageAndWaitForResponse(endpoint, message); task.Con
任务
,该任务在收到响应时完成,以便代码可以像这样调用该方法:
// Send the 'message' to the given 'endpoint' and then wait for the response
Task<IResult> task = sender.SendMessageAndWaitForResponse(endpoint, message);
task.ContinueWith(
t =>
{
// Do something with t.Result ...
});
public void CompleteWaitForResponseResponse(int endpoint, IResult value)
{
if (m_TaskSources.ContainsKey(endpoint))
{
var source = m_TaskSources[endpoint];
source.SetResult(value);
m_TaskSources.Remove(endpoint);
}
}
public class Result : IResult
{
public object Value { get; set; }
}
public interface IResult
{
object Value { get; set; }
}
现在我想添加一个超时,这样调用代码就不会无限期地等待响应。然而,在.NET4.0上,这是因为没有简单的方法来超时任务。所以我想知道Rx是否能更容易地做到这一点。因此,我提出了以下建议:
private readonly Dictionary<int, Subject<IResult>> m_SubjectSources
= new Dictionary<int, Subject<IResult>>();
private Task<IResult> SendMessageAndWaitForResponse(int endpoint, object message, TimeSpan timeout)
{
var source = new Subject<IResult>();
m_SubjectSources.Add(endpoint, source);
// Send the message here ...
return source.Timeout(timeout).ToTask();
}
public void CompleteWaitForResponseResponse(int endpoint, IResult value)
{
if (m_SubjectSources.ContainsKey(endpoint))
{
var source = m_SubjectSources[endpoint];
source.OnNext(value);
source.OnCompleted();
m_SubjectSources.Remove(endpoint);
}
}
专用只读词典m_SubjectSources
=新字典();
私有任务SendMessageAndWaitForResponse(int端点、对象消息、TimeSpan超时)
{
var source=新主题();
m_SubjectSources.Add(端点,源);
//在这里发送消息。。。
返回source.Timeout(Timeout.ToTask();
}
public void CompleteWaitForResponseResponse(int端点,IResult值)
{
if(m_SubjectSources.ContainsKey(端点))
{
var source=m_SubjectSources[endpoint];
source.OnNext(值);
source.OnCompleted();
m_SubjectSources.Remove(端点);
}
}
这一切似乎都没有问题,但我已经看到一些问题表明
Subject
应该是这样,所以现在我想知道是否有一种更有效的方法来实现我的目标。避免在Rx中使用Subject
的建议往往被夸大了。Rx中必须有事件的源,并且可以将其作为主题
Subject的问题通常是在两个Rx查询之间使用时,否则可能会被连接,或者已经有了到IObservable
(例如Observable.FromEventXXX
或Observable.FromAsyncXXX
等)的明确转换
如果您愿意,您可以使用下面的方法取消字典和多个主题。这将使用单个主题并向客户端返回过滤后的查询
这本身并不是“更好”,这是否有意义取决于您的场景的具体情况,但它节省了大量的主题,并为您提供了一个很好的选项,用于监控单个流中的所有结果。如果您连续发送结果(例如从消息队列),这可能是有意义的
// you only need to synchronize if you are receiving results in parallel
private readonly ISubject<Tuple<int,IResult>, Tuple<int,IResult>> results =
Subject.Synchronize(new Subject<Tuple<int,IResult>>());
private Task<IResult> SendMessageAndWaitForResponse(
int endpoint, object message, TimeSpan timeout)
{
// your message processing here, I'm just echoing a second later
Task.Delay(TimeSpan.FromSeconds(1)).ContinueWith(t => {
CompleteWaitForResponseResponse(endpoint, new Result { Value = message });
});
return results.Where(r => r.Item1 == endpoint)
.Select(r => r.Item2)
.Take(1)
.Timeout(timeout)
.ToTask();
}
public void CompleteWaitForResponseResponse(int endpoint, IResult value)
{
results.OnNext(Tuple.Create(endpoint,value));
}
编辑-回答评论中的其他问题
- 无需处理单个主题-它不会泄漏,并且在超出范围时会被垃圾收集
ToTask
确实接受取消令牌-但这实际上是为了从客户端取消
- 如果远程端断开连接,您可以向所有客户端发送一个包含
results.OnError(exception);
-您需要同时实例化一个新的主题实例
比如:
private void OnRemoteError(Exception e)
{
results.OnError(e);
}
return results.Where(r => r.Item1 == endpoint)
.SelectMany(r => r.Item2.HasError
? Observable.Throw<IResult>(r.Item2.Exception)
: Observable.Return(r.Item2))
.Take(1)
.Timeout(timeout)
.ToTask();
这将以预期的方式向所有客户端显示为错误任务
这也是非常线程安全的,因为订阅以前发送了OnError
的主题的客户端将立即返回一个错误-从那时起,它就死了。准备好后,您可以使用以下工具重新初始化:
private void OnInitialiseConnection()
{
// ... your connection logic
// reinitialise the subject...
results = Subject.Synchronize(new Subject<Tuple<int,IResult>>());
}
你是用VS2010开发的吗?否则,你仍然可以针对.NET 4.0使用,它有TaskEx.Delay
,CancellationTokenSource.CancelAfter
等。不,我是在VS2012上。感谢指向BCL库的指针。我不知道那一个。不用担心,你可能还想读一下:重点是,Task.Delay
在.NET 4.0中不可用。这就是我询问Microsoft.Bcl.Async
的原因。即使这不是一个选项,通过System.Threading.Timer
实现仍然很简单,所以我想知道@Petrik的作用是什么。任务。延迟
不是实现的一部分!!!这只是一个模拟响应的黑客程序。一旦你成功了IObservable
,打开了任务无法使用的选项。您是否需要/想要它们是另一个问题。OP专门询问了Rx中的主题。我当然可以使用System.Threading.Timer
或同等工具,但这会在我需要的基础上增加复杂性,我也不相信自己的技能所有这些线程/同步都很重要。在其他人已经为我解决了一些问题的地方,使用Rx或TPL似乎更好:)@JamesWorld感谢这个解决方案,它比我的方法干净多了。我有两个(有点相关)问题是如何处理任务取消,例如,如果远程端断开连接,我想取消该端所有未完成的任务。目前,我有一个CancellationToken
,我用它来做这件事,但我不确定如何将它融入到您的方法中。第二,我是否必须处理任何东西,或者在任务完成时是否已处理?我假设我最终必须处理主ISubject实例?添加了关于个别客户端错误的更多想法。
return results.Where(r => r.Item1 == endpoint)
.SelectMany(r => r.Item2.HasError
? Observable.Throw<IResult>(r.Item2.Exception)
: Observable.Return(r.Item2))
.Take(1)
.Timeout(timeout)
.ToTask();