2

我正在尝试使用 Task Parallel Library 来实现 fire and forget 功能。通过对 的内联调用Task.Factory.StartNew,一切都按预期工作。但是,我想将Task.Factory.StartNew调用移动到一个单独的类中,以便我可以添加日志记录、错误处理等,并可能在将来升级代码,因为将更好的线程类等添加到 .NET Framework 中,而无需复制代码。

下面是一个我希望通过的单元测试,但事实并非如此。我将不胜感激帮助试图弄清楚如何使这项工作。

[TestFixture]
public class ThreadingServiceFixture
{
    public static bool methodFired = false;

    [Test]
    public void CanFireAndForgetWithThreadingService()
    {
        try
        {
            var service = new ThreadingService();

            service.FireAndForget(() => methodFired = true);

            var endTime = DateTime.Now.AddSeconds(1);

            while(DateTime.Now < endTime)
            {
                //wait
            }

            Assert.IsTrue(methodFired == true);
        }
        finally
        {
            methodFired = false;
        }       
    }

}

public class ThreadingService
{
    public Task FireAndForget(Action action)
    {
        return Task.Factory.StartNew(() => action);
    }
}
4

4 回答 4

3

你不是在执行这个动作,你只是在返回它。

尝试:

return Task.Factory.StartNew(() => action());
于 2012-09-02T18:56:19.250 回答
2

如果是“一劳永逸”,则不需要TaskFireAndForget方法中返回,因为调用者可以得到它Task并取消它(严格来说,调用者会“记住”调用)。

如果您想从许多不从公共继承的服务中调用此方法,ThreadingService您可以通过接口实现扩展方法。

public interface IFireAndForget 
{
     // no member needed.
}

public static class FireAndForgetExtensions 
{
    public static void FireAndForget(this IFireAndForget obj, Action action) 
    {
         // pass the action, not a new lambda
         Task.Factory.StartNew(action);
    }
}


// using
public class ThreadingService : IFireAndForget 
{

}

另请注意,在您的方法中,您必须将 传递actionStartNewinsted 的方法,以传递返回action参数的 lambda。

于 2012-09-02T18:57:50.063 回答
2

您没有在ThreadingService

代码应该类似于

public class ThreadingService
{
    public Task FireAndForget(Action action)
    {
        return Task.Factory.StartNew(() => action.Invoke());
    }
}

附加说明:使用公共字段测试状态是邪恶的。考虑可重复性、维护、以不同的顺序运行测试。你应该bool methodFired在测试里面移动。我还假设有更好的技术来测试这个(但我不确定是哪一个)。

于 2012-09-02T18:52:17.250 回答
1

测试线程代码很困难。基于时间的测试是一个坏主意,它们可能会变得不确定,并且您可能会在构建服务器上观察到不稳定的行为。想象一个有时通过有时不通过的测试!

您的代码有一个错误,因为您实际上并没有调用该操作。

但考虑这种变化:

[Test]
[TimeOut(5000)]
public void CanFireAndForgetWithThreadingService()
{
   var service = new ThreadingService();
   ManualResetEvent mre = new ManualRestEvent(bool); // I never remember what is the default...

   service.FireAndForget(() => mre.Set() /*will release the test asynchroneously*/);
   mre.WaitOne(); // blocks, will timeout if FireAndForget does not fire the action.
}

是的,我们仍在使用计时。但是只有在代码中断时才会发生测试超时!在所有其他情况下,测试是绝对可预测的,执行时间非常短,无需等待和祈祷时间问题不会发生;-)

于 2012-09-02T18:58:43.247 回答