4

I was reading The Nature of TaskCompletionSource, a post by Stephen Toub.

public static Task RunAsync(Action action)
{
    var tcs = new TaskCompletionSource<Object>();
    ThreadPool.QueueUserWorkItem(_ =>
    {
        try
        {
            action();
            tcs.SetResult(null);
        }
        catch(Exception exc) { tcs.SetException(exc); }
    });
    return tcs.Task;
}

Since we no longer care what the type of T is, I’ve defaulted to using Object. Then, when the Action is executed successfully, SetResult is still used to transition the Task into the RanToCompletion final state; however, since the actual result value is irrelevant, null is used. Finally, RunAsync returns Task rather than Task<Object>. Of course, the instantiated task’s type is still Task<Object>, but we need not refer to it as such, and the consumer of this method need not care about those implementation details.

I don't particularly understand why the method should return Task rather than Task<object> (which is why I emphasised the bold sentence). I know the method is set to return Task but tcs is a TaskCompletionSource<Object>, not TaskCompletionSource (which is wrong, I think).


Jbehave Ant Task

I'm looking to introduce Jbehave in my project, and I am preparing a simple POC. Using: jbehave 3.9.3, ant 1.9.2, IDE eclipse kepler. I can successfully run the tests from within Eclipse (I've also annotated my test class with @RunWith(JUnitReportingRunner.class) ).

I have, however, some issues when I try running the same via ant.

this is the ant file I'm using:

    <property name="src.dir" value="${basedir}/bdd/jbtest"/>
    <property name="jbehave.version"  value="3.9.3"/>

    <target name="clean">
        <delete dir="target" />
    </target>

    <target name="setup">
        <artifact:dependencies filesetId="dependency.fileset" useScope="test">
            <dependency groupId="org.jbehave" artifactId="jbehave-ant" version="${jbehave.version}"/>           
            <dependency groupId="org.jbehave" artifactId="jbehave-core" version="${jbehave.version}" classifier="resources" type="zip"/>           
            <dependency groupId="org.jbehave.site" artifactId="jbehave-site-resources" version="3.1.1" type="zip"/>           
        </artifact:dependencies>

        <mkdir dir="target" />
        <mkdir dir="target/classes" />
        <mkdir dir="target/lib" />
        <copy todir="target/lib">
            <fileset refid="dependency.fileset" />
            <mapper type="flatten" />
        </copy>
        <!--  copy todir="${src.dir}">
            <fileset dir="../core/src/main/java">       
            </fileset>
        </copy> -->
        <copy todir="target/classes">
            <fileset dir="${src.dir}">
                <include name="**/*.story" />
                <include name="**/*.properties" />
                <include name="**/*.xml" />
            </fileset>
        </copy>



        <path id="story.classpath">
            <fileset dir="${basedir}/lib" includes="**/*.jar" />
            <pathelement location="${basedir}/bin" />
        </path>
        <classloader classpathref="story.classpath" />

        <pathconvert targetos="unix" property="story.classpath.unix" refid="story.classpath">
        </pathconvert>
        <echo>Using classpath: ${story.classpath.unix}</echo>

    </target>

    <target name="compile" depends="setup">
        <javac includeantruntime="false" srcdir="${src.dir}" destdir="bin" debug="on" debuglevel="lines,source" includes="**/*.java,**/*.xml">
            <classpath refid="story.classpath" />
        </javac>
    </target>

    <target name="reports-resources" depends="setup">
        <unzip src="${org.jbehave:jbehave-core:zip:resources}" dest="${basedir}/target/jbehave/view/" />
        <unzip src="${org.jbehave.site:jbehave-site-resources:zip}" dest="${basedir}/target/jbehave/view/" />
    </target>


    <target name="run-stories-as-embeddables" depends="compile, reports-resources">
        <taskdef name="runStoriesAsEmbeddables" classname="org.jbehave.ant.RunStoriesAsEmbeddables" classpathref="story.classpath" />
        <runStoriesAsEmbeddables sourceDirectory="${src.dir}" includes="**/Myjb.java" excludes="**/examples*" batch="false" ignoreFailureInStories="true" ignoreFailureInView="true" generateViewAfterStories="true" 
            systemproperties="java.awt.headless=true,project.dir=${basedir}" />
    </target>


    <target name="run-stories-as-paths" depends="compile, reports-resources" >
        <taskdef name="runStoriesAsPaths" classname="org.jbehave.ant.RunStoriesAsPaths" classpathref="story.classpath" />
        <runStoriesAsPaths sourceDirectory="${src.dir}" 
                includes="**/*.story" batch="false" ignoreFailureInStories="true" ignoreFailureInView="true" generateViewAfterStories="true" 
                systemproperties="java.awt.headless=true,project.dir=${basedir}" 
        >    
        </runStoriesAsPaths>                

    </target>

    <target name="stepdoc" depends="compile">
        <taskdef name="reportStepdocs" classname="org.jbehave.ant.ReportStepdocs" classpathref="story.classpath" />
        <reportStepdocs embedderClass="org.jbehave.examples.core.CoreEmbedder" />

                 <taskdef name="reportRenderer" classname="org.jbehave.ant.ReportRendererTask"  classpathref="story.classpath" />
    <reportRenderer outputDirectory="${basedir}/target/jbehave"
      formats="txt,html" templateProperties="defaultFormats=stats" 
      ignoreFailure="true"/>
    </target>

    <target name="build" depends="run-stories-as-paths,stepdoc" />

</project>

issue #1: I can't specify the format

run-stories-as-paths:
[runStoriesAsPaths] Running stories as paths using embedder Embedder[storyMapper=StoryMapper,storyRunner=StoryRunner,embedderMonitor=AntEmbedderMonitor,classLoader=EmbedderClassLoader[urls=[],parent=java.net.URLClassLoader@1a8fa0f0],embedderControls=UnmodifiableEmbedderControls[EmbedderControls[batch=false,skip=false,generateViewAfterStories=true,ignoreFailureInStories=true,ignoreFailureInView=true,verboseFailures=false,verboseFiltering=false,storyTimeoutInSecs=300,failOnStoryTimeout=false,threads=1]],embedderFailureStrategy=<null>,configuration=org.jbehave.core.configuration.MostUsefulConfiguration@5556d74f,candidateSteps=<null>,stepsFactory=<null>,metaFilters=<null>,systemProperties={java.awt.headless=true, project.dir=D:danielewsjbtest},executorService=<null>,executorServiceCreated=false,storyManager=<null>]
[runStoriesAsPaths] Found story paths: [Example.story, Sample.story]
[runStoriesAsPaths] Processing system properties {java.awt.headless=true, project.dir=D:danielewsjbtest}
[runStoriesAsPaths] System property 'java.awt.headless' set to 'true'
[runStoriesAsPaths] System property 'project.dir' set to 'D:danielewsjbtest'
[runStoriesAsPaths] Using controls UnmodifiableEmbedderControls[EmbedderControls[batch=false,skip=false,generateViewAfterStories=true,ignoreFailureInStories=true,ignoreFailureInView=true,verboseFailures=false,verboseFiltering=false,storyTimeoutInSecs=300,failOnStoryTimeout=false,threads=1]]
[runStoriesAsPaths] Generating reports view to 'D:\daniele\ws\jbtest\target\jbehave' using formats '[]' and view properties '{navigator=ftl/jbehave-navigator.ftl, views=ftl/jbehave-views.ftl, reports=ftl/jbehave-reports-with-totals.ftl, nonDecorated=ftl/jbehave-report-non-decorated.ftl, decorated=ftl/jbehave-report-decorated.ftl, maps=ftl/jbehave-maps.ftl}'
[runStoriesAsPaths] Reports view generated with 0 stories (of which 0 pending) containing 0 scenarios (of which 0 pending)

I did not find a way to pass the format as I'm doing in the java class and that get's ignored, so it does not generate any report.

issue #2 story finding exception

when I run

ant -f jb_ant.xml -lib lib run-stories-as-paths

just after the output shown above, I get an exception

BUILD FAILED
D:\daniele\ws\jbtest\jb_ant.xml:74: org.jbehave.core.io.StoryResourceNotFound: Story path 'Example.story' not found by class loader EmbedderClassLoader[urls=[],parent=java.net.URLClassLoader@1a8fa0f0]
    at org.jbehave.core.io.LoadFromClasspath.resourceAsStream(LoadFromClasspath.java:44)
    at org.jbehave.core.io.LoadFromClasspath.loadResourceAsText(LoadFromClasspath.java:29)
    at org.jbehave.core.io.LoadFromClasspath.loadStoryAsText(LoadFromClasspath.java:38)
    at org.jbehave.core.embedder.StoryRunner.storyOfPath(StoryRunner.java:192)
    at org.jbehave.core.embedder.StoryManager.storyOfPath(StoryManager.java:49)
    at org.jbehave.core.embedder.StoryManager.runningStoriesAsPaths(StoryManager.java:101)
    at org.jbehave.core.embedder.StoryManager.runStories(StoryManager.java:78)
    at org.jbehave.core.embedder.Embedder.runStoriesAsPaths(Embedder.java:203)
    at org.jbehave.ant.RunStoriesAsPaths.execute(RunStoriesAsPaths.java:16)
    at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:292)
    at sun.reflect.GeneratedMethodAccessor8.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:106)
    at org.apache.tools.ant.Task.perform(Task.java:348)
    at org.apache.tools.ant.Target.execute(Target.java:435)
    at org.apache.tools.ant.Target.performTasks(Target.java:456)
    at org.apache.tools.ant.Project.executeSortedTargets(Project.java:1393)
    at org.apache.tools.ant.Project.executeTarget(Project.java:1364)
    at org.apache.tools.ant.helper.DefaultExecutor.executeTargets(DefaultExecutor.java:41)
    at org.apache.tools.ant.Project.executeTargets(Project.java:1248)
    at org.apache.tools.ant.Main.runBuild(Main.java:851)
    at org.apache.tools.ant.Main.startAnt(Main.java:235)
    at org.apache.tools.ant.launch.Launcher.run(Launcher.java:280)
    at org.apache.tools.ant.launch.Launcher.main(Launcher.java:109)

which is puzzling me because jbehave had just listed the found the story while executing the task.

I can post the both the Java classes and stories if this may help diagnose the problem.

Any ideas what am I doing wrong?

4

4 回答 4

4

没有非通用的TaskCompletionSource,考虑到你想要的只是一个没有结果的任务,结果并不重要。在这种情况下,调用者不知道也不关心 Task 实际上是 a Task<object>,调用者只是awaits 它,如果有一个异常,则会得到一个异常。调用者不知道实际结果。

这当然是由于Task<T>继承自Task


找到Task<bool>返回 false 或Task<int>0 的 a 也很常见。

于 2014-07-29T16:06:43.630 回答
4

没有TaskCompletionSource用于创建Task不是Task<T>. TaskCompletionSource<T>当您不关心(或不提供)返回值时,这为泛型类型参数留下了两个选项:

  1. 使用任意现有类型,例如object,作为返回类型。将值设置null为表示任务完成。
  2. 使用特定的非公共类型,并将值设置null为表示任务完成。

当我创建一个没有返回值的TaskCompletionSource<T>实例时Task,我更喜欢使用专用的非公共类型来确保使用代码不会将返回Task的实例误认为Task<T>结果有意义的实例。

private sealed class首先,我定义了以下类(如果它嵌套在另一种类型中,它可以是 a ):

internal sealed class VoidResult
{
}

然后,我不TaskCompletionSource<object>使用完成源,而是使用TaskCompletionSource<VoidResult>. 由于VoidResult调用代码无法访问该类型,因此用户将无法将Task对象强制转换为Task<VoidResult>.

于 2014-07-29T16:10:58.670 回答
2

我不太明白为什么该方法应该返回Task而不是Task<object>

因为当你返回Task<Object>时,意味着当这个方法完成时,它会产生一些有用的 type 值Object。在这种情况下,我们没有产生任何结果,这就是 stephen 选择 return 的原因Task

如果我们正在处理,Func<Object>那么返回Task<Object>是合适的,因为Func会产生一些结果,我们可以选择返回它。

为什么TaskCompletionSource<Object>,不是TaskCompletionSource

因为没有这种东西。没有非通用TaskCompletionSource的。

于 2014-07-29T16:06:50.880 回答
1

如果您返回 a Task<object>,那么var result = await RunAsync(...)将始终返回null,因为这就是您将结果设置为的。

客户不关心这个,所以你只返回一个Task.

理想情况下,您将在TaskCompletionSource内部使用 a 而不是 a TaskCompletionSource<object>,并且只需调用类似的东西SetCompleted()而不是SetResult(null). 但这种类型不存在。

于 2014-07-29T16:06:26.057 回答