Coder Social home page Coder Social logo

itask's Introduction

Build status Coverage Status Total Downloads Latest Stable Version

ITask

Provides an awaitable covariant ITask interface which may be used in place of the built-in Task class.

Purpose

The built-in System.Threading.Tasks.Task and System.Threading.Tasks.Task<TResult> classes allow for compiler support of the async and await keywords. However, since these types must be used as the return values of the methods which leverage the support of the keywords, any interface containing one of these methods may not be covariant over the type TResult. This can cause problems, especially when converting methods on existing interfaces from being synchronous to asynchronous.

This is where the ITask interface comes in. Both ITask and ITask<TResult> interfaces are included for consistency, but the real power lies in the ITask<TResult> interface. It exposes the same functionality as System.Threading.Tasks.Task<TResult>, simply through an interface. Because TResult is only used in the output position for this interface, it is covariant (its definition is public interface ITask<out TResult>) and may be used as a return value within another generic interface without breaking its covariance.

Download

ITask is available as a NuGet package through nuget.org with the package ID MorseCode.ITask.

Usage

Using an ITask within an interface

Given the following interface:

interface ITest<out T>
{
    T ComputeValue();
}

converting the ComputeValue method from being synchronous to ansynchronous would require removing the covariance of interface ITest<T> as follows:

interface ITest<T>
{
    System.Threading.Tasks.Task<T> ComputeValue();
}

With the ITask interface, it is possible to make the ComputeValue method compatible with the await keyword (indicating that it is asynchronous) while maintaining covariance by changing the interface as follows:

interface ITest<out T>
{
    ITask<T> ComputeValue();
}

Awaiting an ITask

The await keyword may be used with an ITask<TResult> in the same manner that it is with a System.Threading.Tasks.Task<TResult>.

For example, within a method marked with the async keyword and given a variable t of type ITest<int>, the await keyword may be used as follows:

int result = await t.ComputeValue();

Returning an ITask

Starting with C#-7, the compiler supports generalized async return types. All you need to do to return an ITask is ensure you’re using a modern compiler (≥VS-15 a.k.a. Visual Studio 2017 or a recent roslyn), a new enough MorseCode.ITask package (support was added in version 1.0.74), and simply mark your method as returning ITask:

public async ITask<int> ComputeValueAsync()
{
    // Do some computing here!
    // The await keyword may be used freely.

    //For example:
    int result1 = await DoSomethingOtherComputingAsync();
    int result2 = await DoSomethingOtherComputingForSomethingElseAsync();
    return result1 + result2;
}

Legacy Compilers

Unfortunately, prior to version 7, C# only supports marking methods with the async keyword if they return a System.Threading.Tasks.Task or a System.Threading.Tasks.Task<TResult>. However, the only reason to use the async keyword is to enable support for the await keyword within that method. As long as the method itself returns an awaitable (which ITask and ITask<TResult> are), then it doesn't matter if that method is marked with the async keyword to callers of the method.

However, we do wish to maintain support for the async keyword for the code within a method returning either an ITask or an ITask<TResult>.

The easiest way to accomplish this is to use the TaskInterfaceFactory class's Create method. This method expects as its only parameter a method returning either a System.Threading.Tasks.Task or a System.Threading.Tasks.Task<TResult> and produces either an ITask or an ITask<TResult>. This method may be defined as a lambda and marked with the async keyword. The result of the call to TaskInterfaceFactory.Create can simply be returned from the method returning the ITask or ITask<TResult> and should be the only statement within that method.

For example, the following is a sample implementation of the ComputeValue method defined above in a class implementing ITest<int>:

public ITask<int> ComputeValue()
{
    return TaskInterfaceFactory.Create(async () =>
        {
            // Do some computing here!
            // The await keyword may be used freely.
            
            //For example:
            int result1 = await DoSomeOtherComputing();
            int result2 = await DoSomeOtherComputingForSomethingElse();
            return result1 + result2;
        });
}

If you already have a System.Threading.Tasks.Task or a System.Threading.Tasks.Task<TResult> and you wish to convert it into an ITask or an ITask<TResult> simply use the AsITask extension method as follows (given a variable t of type System.Threading.Tasks.Task and a variable t2 of type System.Threading.Tasks.Task<int>):

ITask iTask = t.AsITask();
ITask<int> iTask2 = t2.AsITask();

Conversely, the AsTask extension method will convert an ITask or an ITask<TResult> into a System.Threading.Tasks.Task or a System.Threading.Tasks.Task<TResult> as follows (given a variable t of type ITask and a variable t2 of type ITask<int>):

System.Threading.Tasks.Task task = t.AsTask();
System.Threading.Tasks.Task<int> task2 = t2.AsTask();

itask's People

Contributors

bastianeicher avatar binki avatar jam40jeff avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

itask's Issues

Add GetAwaiter() to ease consumption?

In the docs I see:

Note that the functionality required by the compiler to enable await keyword support is included in a class in the MorseCode.ITask namespace. Therefore, a using statement will need to be added to the file for this code to compile as follows:

using MorseCode.ITask;

I think that if GetAwaiter() were added to the ITask and ITask<TResult> interfaces, this would be unnecessary. I haven’t actually tested that, though.

This would be a breaking change for anyone trying to implement ITask or ITask<TResult>. The cleanest way to avoid issues with the break might be to rename the nuget package itself to something like MorseCode.ITask2.

VS Debugger Stops Working After Returning from "await" Call

Create a .NET Core Standard 2.1 CLI project in Visual Studio, then use this code, set a breakpoint at the "await" line, then run and step through the code. When the debugger is finished stepping through the "GetPackage" function, as soon as it returns from the function, the program immediately executes the rest of the code and completes without the expected behavior of the debugger continuing to the next "int g = 9;" line, stopping, and waiting.

Any idea why the ITask is breaking the VS debugger?

using MorseCode.ITask;
using System;
using System.Threading.Tasks;

namespace ConsoleApp1_AsyncBugTest_NETCore
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Test test = new Test();
            int j = 0;
            await test.GetPackage();

            int g = 9;

            Console.WriteLine("Hello World!");
        }

        public class PackageBase
        {

        }

        public class NullPackage : PackageBase
        {

        }


        public class Test
        {
            NullPackage nullPackage;
            public async ITask<NullPackage> GetPackage()
            {
                if (nullPackage == null)
                    nullPackage = new NullPackage();

                return await Task.FromResult(nullPackage).AsITask();
            }
        }
    }
}

Edit/Update: If I remove the ITask and revert back to using a regular Task, the debugger acts as expected, so it's definitely a problem with ITask.

Support building in VS 2017

When I build in VS 2017, the build is left in such a state that I get a bunch of errors about ccrewrite needing to be called. When I build in VS, it’d be nice if it could run ccrewrite automatically. That way I can figure out how to run the test suite correctly when trying to make changes.

Support csharp7 Generalized async return type alternative to TaskInterfaceFactory.Create

Starting with csharp7 (regardless of targeted framework), supposedly any type can be used as a return type for an async method as the type is decorated with instructions to the compiler on how to build the type.

https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-7#generalized-async-return-types

It doesn’t really describe how to actually use generalized return types. And I haven’t actually tried it myself yet. Also, my only concern for this possibly not working is there’s a chance the compiler has some (unintentional, I think) inability to support interface (I only know of official examples for struct (ValueTask<TResult>) and class (Task<TResult>)), but that would probably be fixed by roslyn people if that is an issue. Below is what I think I understand so far how this works in csharp7:

  1. Create a class System.Runtime.CompilerServices.AsyncMethodBuilderAttribute internal to the assembly (the compiler just matches the assembly-local type name; this is purposeful to avoid requiring people to use the latest framework or reference external nugets).
  2. Implement the method builder which the compiler will delegate to which builds the actual ITask<TResult> similar to AsyncValueTaskMethodBuilder.
  3. Decorate your type (ITask<TResult> in this case) with your attribute similar to how ValueTuple<TResult> is decorated.

I suspect that the whole “method builder” might end up requiring implementation of some logic which wasn’t needed for ITask<TResult>.

However, if this could be done, it would make implementing ITask<TResult> methods a ton cleaner. I think I am going to play with ITask and see if I can get a working implementation for it in my spare time.

Covariant return types with ITask

Not really an issue per say, just wanted to suggest that you update your documentation to reflect that this also works really well with covariant return type support that was added in C# 9.0.

Here's a small example if you want one for your tests or docs:

[TestFixture]
public class MyTests
{
    [Test]
    public async Task MyTest()
    {
        var pz = new PettingZoo();
        var an = await pz.GetAnimal().ConfigureAwait(false);
        Assert.AreEqual("Cat", an.Name);
    }
}

public class Zoo
{
    public virtual async ITask<Animal> GetAnimal()
    {
        await Task.Delay(0).ConfigureAwait(false);
        return new Animal();
    }
}

public class PettingZoo : Zoo
{
    public override async ITask<Cat> GetAnimal()
    {
        await Task.Delay(0).ConfigureAwait(false);
        return new Cat();
    }
}

public class Animal
{
    public string Name { get; set; } = "Animal";
}

public class Cat : Animal
{
    public Cat()
    {
        Name = "Cat";
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.