【问题标题】:Awaiting a TaskCompletionSource<T> that never returns a Task等待一个从不返回任务的 TaskCompletionSource<T>
【发布时间】:2016-01-16 04:35:12
【问题描述】:

我正在尝试围绕异步发布/订阅系统编写单元测试。在我的单元测试中,我创建了一个 TaskCompletionSource&lt;int&gt; 并在订阅回调中为其分配一个值。在订阅回调中,我取消订阅出版物。下次发布时,我想验证回调是否未命中。

[TestMethod]
[Owner("Johnathon Sullinger")]
[TestCategory("Domain")]
[TestCategory("Domain - Events")]
public async Task DomainEvents_subscription_stops_receiving_messages_after_unsubscribing()
{
    // Arrange
    string content = "Domain Test";
    var completionTask = new TaskCompletionSource<int>();

    DomainEvents.Subscribe<FakeDomainEvent>(
        (domainEvent, subscription) =>
        {
            // Set the completion source so the awaited task can fetch the result.
            completionTask.TrySetResult(1);
            subscription.Unsubscribe();
            return completionTask.Task;
        });

    // Act
    // Publish the first message
    DomainEvents.Publish(new FakeDomainEvent(content));
    await completionTask.Task;

    // Get the first result
    int firstResult = completionTask.Task.Result;

    // Publish the second message
    completionTask = new TaskCompletionSource<int>();
    DomainEvents.Publish(new FakeDomainEvent(content));
    await completionTask.Task;

    // Get the second result
    int secondResult = completionTask.Task.Result;

    // Assert
    Assert.AreEqual(1, firstResult, "The first result did not receive the expected value from the subscription delegate.");
    Assert.AreEqual(default(int), secondResult, "The second result had a value assigned to it when it shouldn't have. The unsubscription did not work.");
}

当我这样做时,测试在第二个await 挂起。我知道这是由于任务永远不会返回而发生的。我不确定如何解决它。我知道我可以轻松地创建一个本地字段,我只需像这样分配值:

[TestMethod]
[Owner("Johnathon Sullinger")]
[TestCategory("Domain")]
[TestCategory("Domain - Events")]
public void omainEvents_subscription_stops_receiving_messages_after_unsubscribing()
{
    // Arrange
    string content = "Domain Test";
    int callbackResult = 0;

    DomainEvents.Subscribe<FakeDomainEvent>(
        (domainEvent, subscription) =>
        {
            // Set the completion source so the awaited task can fetch the result.
            callbackResult++;
            subscription.Unsubscribe();
            return Task.FromResult(callbackResult);
        });

    // Act
    // Publish the first message
    DomainEvents.Publish(new FakeDomainEvent(content));

    // Publish the second message
    DomainEvents.Publish(new FakeDomainEvent(content));

    // Assert
    Assert.AreEqual(1, firstResult, "The callback was hit more than expected, or not hit at all.");
}

这感觉不对。这假设我从未在整个堆栈中执行等待操作(当他们是订阅者时我会这样做)。此测试不是安全测试,因为测试可能会在发布完全完成之前完成。这里的意图是我的回调是异步的,并且发布是非阻塞的后台进程。

在这种情况下如何处理 CompletionSource?

【问题讨论】:

  • 听起来像 ManualResetEvent 或 CountdownEvent 会更合适。
  • 我还要说我认为 Publish 应该返回一个任务,如果它还没有的话。这非常有用,因为调用者可以等待,如果他们只希望在所有订阅者都处理完事件后进行处理。它也会使测试变得微不足道。只需等待发布并确保计数器为 1。
  • 这个场景的目的是为 DomainEvents 提供一个异步工作流,同时保持我的域模型同步。我不想让整个域层异步来促进订阅事件的数据和服务层。如果我让Publish 方法返回Task 并且不在我的域中等待它,那么在使用TPL 时这不是一个不好的做法吗?
  • 我更改了我的 Publish 以继续并返回 Task。 My Domain 不必等待它,所以这没什么大不了的。我只是不确定在使用 TPL 时是否通常不赞成这一点。感觉就像我正在更改我的代码以促进测试,我不应该这样做:/
  • 还有其他可能性。一种是在第二次发布后简单地等待 TCS 任务超时,而不是等待,比如 5 秒。任务永远不应该完成。另一种选择可能是使用您知道将在第一个处理程序之后调用的另一个处理程序,尽管这可能依赖于订阅排序中的实现细节。测试没有发生的事情可能很困难。

标签: c# asynchronous task taskcompletionsource


【解决方案1】:

很难测试某件事永远不会发生。你能做的最好的事情就是测试它没有在合理的时间内发生。我有一个异步协调原语库,为了对这个场景进行单元测试,我不得不求助于只观察任务一段时间,然后假设成功 (see AssertEx.NeverCompletesAsync)。

不过,这不是唯一的解决方案。从逻辑上讲,也许最干净的解决方案是伪造时间本身。也就是说,如果你的系统有足够的假时间系统的钩子,那么你实际上可以编写一个测试来确保永远不会调用回调。这听起来很奇怪,但它非常强大。缺点是它需要大量的代码修改——不仅仅是返回一个Task。如果你有兴趣,Rx is the place to start,他们的TestScheduler type

【讨论】:

    猜你喜欢
    • 2014-06-08
    • 2021-09-28
    • 2017-06-02
    • 1970-01-01
    • 1970-01-01
    • 2016-09-10
    • 2014-10-01
    • 1970-01-01
    相关资源
    最近更新 更多