0

Hello I have the following problem:

I want to perform something similar to a transaction. I want to execute a number of async operations after I receive an external trigger.Therefore I am using a TaskCompletionSource that gets set in a method representing the trigger :TriggerTransaction.

This trigger method gets called in Main on the thread pool when i press a specific console key.

After I press the A keyword the TriggerTransaction gets executed and the TaskCompletionSource-s get set.Still the main thread does not compute the sum of the two awaited tasks.

     class Program
                {
                    public static Task<Task<int>> TransactionOperation1()
                    {
                        TaskCompletionSource<Task<int>> tcs = new TaskCompletionSource<Task<int>>();
                        tasks.Add(tcs);
                        Task<Task<int>> result = tcs.Task;
                        return result;
                    }
                    public static Task<Task<int>> TransactionOperation2()
                    {
                        TaskCompletionSource<Task<int>> tcs = new TaskCompletionSource<Task<int>>();
                        tasks.Add(tcs);
                        Task<Task<int>> result = tcs.Task;
                        return result;
                    }

                    public static async Task<int> ExecuteTransactionOnDB()
                    {
                        await Task.Delay(1000);
                        return 5;
                    }

                    public static async Task TriggerTransaction()
                    {
                        int value = await ExecuteTransactionOnDB();
                        foreach (var item in tasks)
                        {
                            item.SetResult(value);
                        }
                    }
                    public static List<dynamic> tasks = new List<dynamic>();

                    static async Task Main(string[] args)
                    {
                        Task<Task<int>> a = TransactionOperation1();
                        Task<Task<int>> b = TransactionOperation2();
                        Task.Run(async() =>
                        {
                            while (Console.ReadKey().Key != ConsoleKey.A) ;
                            await TriggerTransaction();
                        });
                        if (!File.Exists("D:\\data.txt"))
                        {
                            File.Create("D:\\data.txt");
                        }
                        using(FileStream stream=new FileStream("data.txt",FileMode.Append,FileAccess.Write))
                        {
                        int sum=await await a + await await b;//thread wont pass this line when tasks are set.
                        ReadOnlyMemory<byte> bytes = Encoding.UTF8.GetBytes(sum);

                            stream.Write(bytes.ToArray());
                        }
                        Console.WriteLine(await await a + await await b);

                    }
                }
        }

P.S If you are wondering why I did use a List<dynamic> to store the TaskCompletionSource-s ,it's because the TransactionOperations will differ in return type.Some of them will return int,others String ..Bool..etc.

For a better understanding i made a schema- As you will see there are:
-A list where i want to store the TCS-es
-Some Calls that are completed only after the external trigger was set(the transaction was executed)
As you can see in the Calls,all have different return types.
enter image description here

Bercovici Adrian
  • 8,794
  • 17
  • 73
  • 152
  • 1
    I think you've probably misunderstood something. It's rare to need to create `TaskCompletionSource`s at all (outside of adapting EAP async APIs to TAP). Tasks wrapping other Tasks is also generally rarely right. Unfortunately, the bulk of your code seems to be about doing these two things so I can't even tell what the problem you're trying to solve *is*. – Damien_The_Unbeliever Aug 02 '18 at 07:57
  • 1
    @BercoviciAdrian this looks like an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). You have a problem X and think that Y is the solution, multiple TCSs and a list? When that doesn't work, you ask about X, not Y. What is the real problem you want to solve? Could it be solved with a simple `await Task.WhenAll()`? Or some `ActionBlock` instances? – Panagiotis Kanavos Aug 02 '18 at 08:17
  • If i have a `Task` and a `Task` how can i use `Task.WhenAll` and grab both results? – Bercovici Adrian Aug 02 '18 at 08:19
  • @BercoviciAdrian what are you trying to do *at all*? This has nothing to do with transactions - atomic operations that either complete or get rolled back. Transactions aren't related to tasks. Your question is all about how you tried to solve *some* problem, not the problem itself – Panagiotis Kanavos Aug 02 '18 at 08:22
  • If you want transactions, check TransactionScope. If you want to implement your own transactional objects (they are called "resource managers") you need to implement the [IEnlistmentNotification](https://msdn.microsoft.com/en-us/library/system.transactions.ienlistmentnotification(v=vs.110).aspx). This [article](https://blog.goyello.com/2016/11/30/let-net-framework-care-about-transactions-handling-for-you-by-implementing-ienlistmentnotification/) shows how to do it, it's easier than it sounds – Panagiotis Kanavos Aug 02 '18 at 08:25
  • I am performing multiple `async` method calls, each method having a different return type.In each of these calls i have to perform an `async` operation **AFTER** i receive a trigger from somewhere else .This "trigger" represents the `Execution` of the transaction. – Bercovici Adrian Aug 02 '18 at 08:26
  • @BercoviciAdrian that's still a description of the code, not the problem. Getting the results of completed tasks without blocking only requires checking `.Result`. And probably all of it could be a single ActionBlock, or a single *call* to the two async methods. What are you trying to do ?! – Panagiotis Kanavos Aug 02 '18 at 08:29
  • @BercoviciAdrian I need to download multiple payment records from multiple banks every day. I don't use raw tasks and lists. I create a pipeline of TPL Dataflow blocks, each one performing *one* task on an input message. One block to get the list of transactions and forward it to the block that downloads each transaction, then on to the parser and bulk import blocks. I only post the dates I want to the starting block and wait for the ending block to finish. No TCS, no task lists. Getting, processing, importing the transactions is the problem. Dataflow is how I solved it – Panagiotis Kanavos Aug 02 '18 at 08:31

2 Answers2

1

Why would you need a Task<Task<int>>? Simply Task<int> is enough, and accordingly, TaskCompletionSource<int>. And you also get rid of an awkward await await ..., which isn't required in your case either.

Note that I also added Close() to the stream returned by File.Create().

Here is a working version of the program:

class Program
{
    public static Task<int> TransactionOperation1()
    {
        TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
        tasks.Add(tcs);
        return tcs.Task;
    }
    public static Task<int> TransactionOperation2()
    {
        TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
        tasks.Add(tcs);
        return tcs.Task;
    }
    public static async Task<int> ExecuteTransactionOnDB()
    {
        await Task.Delay(1000);
        return 5;
    }
    public static async Task TriggerTransaction()
    {
        int value = await ExecuteTransactionOnDB();
        foreach (var item in tasks)
        {
            item.SetResult(value);
        }
    }

    public static List<dynamic> tasks = new List<dynamic>();

    static async Task Main(string[] args)
    {
        Task<int> a = TransactionOperation1();
        Task<int> b = TransactionOperation2();
        Task input = Task.Run(async () => {
            while (Console.ReadKey().Key != ConsoleKey.A);
            await TriggerTransaction();
        });
        if (!File.Exists("C:\\temp\\data.txt"))
        {
            File.Create("C:\\temp\\data.txt").Close();
        }
        using (FileStream stream = new FileStream("C:\\temp\\data.txt", FileMode.Append, FileAccess.Write))
        {
            int sum = await a + await b; // now it works ok
            var bytes = Encoding.UTF8.GetBytes(sum.ToString());
            stream.Write(bytes);
        }

        Console.WriteLine(await a + await b);
    }
}
felix-b
  • 8,178
  • 1
  • 26
  • 36
1

Check out the modified version of the code, it produce the expected result, by executing the Task created using TaskCompletionSource. I have made the code the Generic too, so that you don't need to use the dynamic type and define the datatype at the compile time

static async Task Main(string[] args)
{
    var a = Program<int>.TransactionOperation1();
    var b = Program<int>.TransactionOperation2();
    await Task.Run(async() =>
    {
    Console.ReadLine();
    await Program<int>.TriggerTransaction(5);
    });

    if (!File.Exists("D:\\data.txt"))
    {
    File.Create("D:\\data.txt");
    }

    using (FileStream stream = new FileStream("D:\\data.txt", FileMode.Append, FileAccess.Write))
    {
        int sum = await a + await b;//thread wont pass this line when tasks are set.        
        var bytes = Encoding.UTF8.GetBytes(sum.ToString());     
        stream.Write(bytes, 0, bytes.Length);
    }

    Console.WriteLine(await a + await b);
}

class Program<T>
{
    public static Task<T> TransactionOperation1()
    {
        var tcs = new TaskCompletionSource<T>();
        tasks.Add(tcs);
        return tcs.Task;
    }

    public static Task<T> TransactionOperation2()
    {
        var tcs = new TaskCompletionSource<T>();
        tasks.Add(tcs);
        return tcs.Task;
    }

    public static async Task<T> ExecuteTransactionOnDB(T t)
    {
        return await Task.FromResult(t);
    }

    public static async Task TriggerTransaction(T t)
    {
        T value = await ExecuteTransactionOnDB(t);

        foreach (var item in tasks)
        {
            item.SetResult(value);
        }
    }

    public static List<TaskCompletionSource<T>> tasks = new List<TaskCompletionSource<T>>();

}

Following are the important modifications:

  1. List<dynamic> is replaced by List<TaskCompletionSource<T>>
  2. TransactionOperation1/2 have return type Task<T>, which is the Task created using the TaskCompletionSource<T>
  3. Added an extra await to the Task.Run, which executes the TriggerTransaction internally, though you can replace the following code:

     await Task.Run(async() =>
     {
       Console.ReadLine();
       await Program<int>.TriggerTransaction(5);
     });
    

    with await Program<int>.TriggerTransaction(5);

Now it produces the result as you expect, it will sum up the two integers. Few more small changes like removing Task.Delay, which is not required

EDIT 1 - Using Task.WhenAll

static async Task Main(string[] args)
{
    var a = Program.TransactionOperation1(5);
    var b = Program.TransactionOperation1(5);

    Console.ReadLine();

    var taskResults  = await Task.WhenAll(a,b);


    dynamic finalResult = 0;

    foreach(var t in taskResults)
        finalResult += t;


    if (!File.Exists("D:\\data.txt"))
    {
        File.Create("D:\\data.txt");
    }

    using (FileStream stream = new FileStream("D:\\data.txt", FileMode.Append, FileAccess.Write))
    {
        var bytes = Encoding.UTF8.GetBytes(finalResult.ToString());
        stream.Write(bytes, 0, bytes.Length);
    }

    Console.WriteLine(finalResult);
}

class Program
{
    public static Task<dynamic> TransactionOperation1(dynamic val)
    {
        return Task<dynamic>.Run(() => val);
    }

    public static Task<dynamic> TransactionOperation2(dynamic val)
    {
        return Task<dynamic>.Run(() => val);
    }

}
Mrinal Kamboj
  • 11,300
  • 5
  • 40
  • 74
  • 1
    The problem is that the TCSs and task list are themselves redundant. Which makes the *rest* of the code redundant as well. Could everything be replaced with a single `await Task.WhenAll()`? Or a single ActionBlock ? – Panagiotis Kanavos Aug 02 '18 at 08:15
  • The thing is that i can't use generics since i expect different return types from TCS.Also you are indeed right ,i wanted to use `WhenAll` but how can you get granularity over the different return types? My list could have a `TCS>` and a `TCS>`.How can you get your hands on them? – Bercovici Adrian Aug 02 '18 at 08:18
  • 1
    @PanagiotisKanavos certainly yes, we can use the `Task.WhenAll` to execute the aggregated Tasks, there's nothing special achieved using `TaskCompletionSource`, since no user triggered event is involved, though I was just modifying OP's code – Mrinal Kamboj Aug 02 '18 at 08:21
  • 1
    @BercoviciAdrian that should be simple, deal with `Task.WhenAll`, internally you get a execute `Task`, which is the base type for `Task`, here you may use `Task` and still do what ever operation you need, till the point internal type supports it – Mrinal Kamboj Aug 02 '18 at 08:24
  • 1
    @BercoviciAdrian making code generic was just a value add, you can certainly continue using the non generic version and work with dynamic type till it internally support the operation like +, which you have defined – Mrinal Kamboj Aug 02 '18 at 08:27
  • 1
    Check out the modified version **(EDIT1)** using `Task.WhenAll`, which certainly takes out lot of complexity and makes the code simpler. Have used **dynamic** type for processing here. – Mrinal Kamboj Aug 02 '18 at 08:45