Results 1 to 8 of 8

Thread: [RESOLVED] Task(Of TResult).Factory.ContinueWith with chain of functions, methods and results

  1. #1

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2011
    Location
    England
    Posts
    421

    Resolved [RESOLVED] Task(Of TResult).Factory.ContinueWith with chain of functions, methods and results

    Hi everyone and thanks for reading..

    I am getting myself mixed up while thinking about chaining multiple methods together using a single task.

    Okay, so, I am passing multiple methods and their respective argument (always a single object) into a collection. When all the methods I want to run have been added to the collection, I run the methods in turn using a single task for all of the methods in the collection.

    So for example, I am able to run the methods in turn by using the following:

    VB.NET Code:
    1. 'Me.Token is a CancellationToken used to exit the task if a cancellation was requested.
    2. 'Item is a Class with two properties [.Method is an Action(Of Object) and .Argument is an Object]
    3. Return Task.Factory.StartNew(
    4.     Sub()
    5.         Me.Token.ThrowIfCancellationRequested()
    6.  
    7.         For Each Item As TaskItem In Me.List
    8.             Task.Factory.StartNew(Item.Method, Item.Argument, Me.Token, TaskCreationOptions.AttachedToParent, TaskScheduler.Current)
    9.         Next Item
    10.     End Sub, Me.Token)

    I would now like to extend on this and to run a series of Functions in a chained task and to collect the results of each function within that chain. My understanding is that you can pass the result of one function to the parameter of the next by using the ContinueWith method however to also collect the results I think I would need to do this with the use of a Task(Of TResult).

    So what I would like to know is - if I start with the following (which will run the first function from within my collection and collect the result):

    VB.NET Code:
    1. Dim T As Task(Of Object) = Task(Of Object).Factory.StartNew(Me.List.Item(0).Method, Me.List.Item(0).Argument, Me.Token, TaskCreationOptions.AttachedToParent, TaskScheduler.Current)

    What do I need to do to allow ContinueWith to run each of my remaining functions while passing the TResult of the previous function to the next.
    VB.NET Code:
    1. For iChild As Integer = 1 To Me.Count - 1
    2.     ' What should go here.
    3.     T = T.Factory.ContinueWith...
    4. Next iChild
    I am really struggling to understand the MSDN documentation on this (it wants a func(of object, object) for the first parameter of the ContinueWith method and I am not sure how to go about that). I cannot find an example close enough to what I am doing in order to follow it.


    I hope I have made sense, but let me know if I need to be any clearer.

    Cheers,
    Jay

  2. #2
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Task(Of TResult).Factory.ContinueWith with chain of functions, methods and result

    My first thought is this is overcomplicating things. All programs are essentially chains of function calls. This is what a "chain" of functions collecting results might look like:
    Code:
    Function DoChain() As Something
        Dim result = GetFinalResult(...)
        Return result
    End Function
    
    Function GetFinalResult(...) As Something
        Dim accumulatedResults = Step1(...)
        accumulatedResults = Step2(...)
        ...
        Return accumulatedResults
    End Function
    Tasks weren't specifically made for this. They were made for running functions asynchronously. Continuations weren't created for building chains of functions that feed each other. They were built for specifying what to do when a task completes. Both of these together implement the Async/Await keywords that make BackgroundWorker look obsolete.

    That doesn't mean you can't use them to build chains of functions, but it might mean it will be harder than it looks.

    Before I get too much deeper, I want to make sure I understand what you're trying to do. I wrote some code above that looks like how you'd write it without tasks. Can you write a little bit of code without Tasks that shows what you're trying to do? Then we could come up with a better idea of if and how Tasks might help.

    By the way, which VS are you using? Task.Factory is VS2010 syntax, I think? It's a little more streamlined in newer versions.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  3. #3

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2011
    Location
    England
    Posts
    421

    Re: Task(Of TResult).Factory.ContinueWith with chain of functions, methods and result

    Thanks Sitten Spynne. I have read a lot of your helpful posts over the years and appreciate you taking the time to contribute.

    The first thing I will add, that will be relevant here, is that my overall aim is to create a DLL that can chain any combination of methods/functions together and pass parameters between them (if necessary) and then raise an event back to the control that called the DLL once the tasks have all completed.

    Also I am using VS2015.

    So as an example this is a scenario I am working on at the mo (without tasks):
    VB.NET Code:
    1. Public Class Parameters
    2.   Public Property MySQLConnectionString As String
    3.   Public Property ID As Integer
    4.   Public Property Query As String
    5.   Public Property Result As String
    6.   Public Property Success As Boolean
    7. End Class
    8.  
    9. Public Sub Method(Param As Object)
    10.   'Connect to a MySQLDatabase and SELECT multiple columns into a datatable.
    11.   Dim MethodParams As Parameters = CType(Param, Parameters)
    12.   Dim DT As DataTable = Nothing
    13.   '...
    14.   'E.g. DT could end up with two columns, 'ID' and 'Value'.
    15.   '...
    16.   For Each DRow As DataRow In DT.Rows
    17.     '...
    18.     'Loop through the datatable and perform a lengthy function for each row.
    19.     '...
    20.     Dim ChainParams As New Parameters
    21.     ChainParams.MySQLConnectionString = MethodParams.MySQLConnectionString
    22.     ChainParams.ID = CInt(DRow.Item("ID"))
    23.     ChainParams.Query = "QueryBasedOnDRow.Item('Value')"
    24.     ChainOne(ChainParams)
    25.   Next DRow
    26.   '...
    27.   'Wait on the completion of all the Chained Methods
    28.   '...
    29.    'Raise a Completion Event and attach a class with some stats as a parameter based on the results collected from the Chained functions.
    30. End Sub
    31.  
    32. Public Function ChainOne(Param As Object) As Parameters
    33.   'Connect to a MySQLDatabase and run a query from Method().
    34.   Dim Params As Parameters = CType(Param, Parameters)
    35.   Params.Result = "ResultFromLengthyMySQLDatabaseQuery"
    36.  
    37.   Return Params
    38. End Function
    39.  
    40. Public Function ChainTwo(Param As Object) As Parameters
    41.   'Perform final part of the chain and record some results.
    42.   Dim Params As Parameters = CType(Param, Parameters)
    43.  
    44.   If CBool("SomeLogic") = True Then
    45.     '...
    46.     'Update the MYSQL Table with the result of ChainTwo into the row with the ID from Method().
    47.     '...
    48.     Params.Success = True
    49.   Else
    50.     Params.Success = False
    51.   End If
    52.  
    53.   'Collect some stats for a completion event.
    54.   Return Params
    55. End Function
    In this scenario the Method retrieves over 10,000 rows of data from a MySQL table. Then it needs to run a query for each of those rows and update the results back to the table in the correct row. What got me started on this journey into tasks, is that I want to be able to run several of the 'Chained' functions on simultaneous threads because each can take several seconds to run. However at the same time I want to limit how many threads will run at once so I can control how many database connections are being used. I started to use collections to control this after I ran into difficulty limiting the number of tasks used AND allowing them to take a parameter and produce a result.

    So my aim (in this particular scenario) is to start Method() on a task of it's own to keep the UI thread from becoming unresponsive. When I loop through the DataTable in MethodOne I want to add each reference of the ChainOne() function and it's associated parameter to a collection. For example there may be 8 collections, representing 8 threads.

    Each collection has a method to loop through it's items and run the ChainOne function's in turn and thus keeps them all running on a single task per collection. But - I have this working fine where I do not need to pass a parameter from a ChainOne function to a ChainTwo function and then collect a result from the ChainTwo function to pass back to an event raised by the collection when it finishes all of the functions.

    I hope this makes sense, as my head is still spynning (sorry, pun intended) from thinking this over.

    Finally, and as I said at the beginning, because I want this to be part of a DLL I want to be able to make sure I can simply call upon it with any combination of Methods and Functions and therefore finding a specific approach for the specific scenario I mentioned is not my only goal here.

    Thanks,
    Jay

  4. #4
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Task(Of TResult).Factory.ContinueWith with chain of functions, methods and result

    I have some ideas. I'm sorry it's taking me so long, I'm having a hard time shaking an old project where I did this out of my head. In that project, customers could add new functions, so our code had to handle any combination of types, including some we'd obviously never seen. That was really, really hard, and it's making me view this problem as really, really hard. I still need some time to think about it, but here's some ideas.

    The simplest implementation of a chain of functions is an array of some delegate.
    Code:
    Dim operations As Func(Of Double, Double)() = { Add, Subtract }
    
    Dim result As Double = 0
    For Each operation In Operations
        result = operation(result)
    Next
    I thought about a lot of things at this point. One of them's important, but let's ignore it because it's a problem with every implementation. The next thing I thought about was, "How would this be represented via tasks and continuations?" Using the imaginary Add() and Subtract() above, and pretending VB syntax isn't stupid about line endings:
    Code:
    Dim chainTask = Task(Of Double).Run(Add)
        .ContinueWith(Sub(parent) Subtract(parent.Result))
    
    Dim result = chainTask.Result
    That looks inferior to an array, to me, because by the time we've got a fifth "chain" things look ridiculous. It does handle exceptions for us, but when I think about "Is this 20-element array in the right order?" at debug time vs. "Are the continuations in the right order?" I barf thinking about Tasks.

    This is where things can get REALLY complicated, because it's very hard to put functions with different signatures into a data structure AND maintain integrity.

    What I mean is it's hard to represent this chain:
    Separate an integer into its digits. Sum the digits, then multiply the digits by three.

    (Integer -> Integer()) -> (Integer(), Integer) -> (Integer, Integer)
    It's not impossible. But it's also a lot easier if you can narrow it to things like, "I'll only ever use Integer", or "If Double is at the front, Double will be at the end". I keep trying to come up with examples, and they always end up with that kind of complexity.

    Tell me, does this sound more like your use case? (I know it says 'a customer'. Assume this is happening in parallel with many customers.)
    I retrieve a customer. I gather some sales information and modify the customer's status in some way. I gather some other information and update the customer again. Finally, I save the updated customer to a database.

    (Id, Customer) -> (Customer, Customer) -> (Customer, Customer) -> Action(Customer)
    That sort of workflow is much more reasonable to implement. If you agree that all the steps in the middle should have the same input/output types, I think we're in business with a type that looks something like this:
    Code:
    Public Class CustomerWorkflow
    
        Public Sub Execute(ByVal id As Integer)
            Dim customer = FetchCustomer(id)
    
            customer = ExecuteSteps(customer)
    
            SaveCustomer(customer)
        End Sub
    
        ...
    End Class
    I think that setup can still benefit from task parallelization, but I want to make sure this covers it before I talk about it. I've got a lot of other stuff to do today so it might be tomorrow morning!
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  5. #5

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2011
    Location
    England
    Posts
    421

    Re: Task(Of TResult).Factory.ContinueWith with chain of functions, methods and result

    Hi, the customer workflow suggestion does look more like the kind of implementation I am trying to achieve. My plan is to always use the same Class as input and output from one function to the next throughout the chain of tasks.

    Also, please take your time as this is purely for personal projects so I am not in any rush.

    Thanks,
    Jay

  6. #6
    Super Moderator FunkyDexter's Avatar
    Join Date
    Apr 2005
    Location
    An obscure body in the SK system. The inhabitants call it Earth
    Posts
    7,957

    Re: Task(Of TResult).Factory.ContinueWith with chain of functions, methods and result

    I haven't had time to take in the whole thread but I think you're looking for the command pattern

    edit>...and here's a handy link on passing parameters between commands
    Last edited by FunkyDexter; Feb 16th, 2016 at 09:28 AM.
    The best argument against democracy is a five minute conversation with the average voter - Winston Churchill

    Hadoop actually sounds more like the way they greet each other in Yorkshire - Inferrd

  7. #7
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Task(Of TResult).Factory.ContinueWith with chain of functions, methods and result

    That's more or less going to be my suggestion, especially if you don't have a lot of variety in parameters.

    Here's a watered-down version of a really common class in modern development: RelayCommand:
    Code:
    Public Class RelayCommand
        
        Private Func(Of Customer, Customer) _execute
    
        Public Sub New(ByVal Func(Of Customer, Customer) execute)
            _execute = execute
        End Sub
    
        Public Function Execute(ByVal customer As Customer) As Customer
            Return _execute(customer)
        End Function
    End Class
    It is, essentially, a Func(Of Customer, Customer). The idea is you can derive more specialized classes that do a little more work. Anyway, your "pipeline" here will consist of several of those commands chained together, with a start/end step:
    Code:
    Public Class Pipeline
    
        Private _commands As List(Of RelayCommand)
    
        Public Sub AddCommand(...)
    
        Public Sub Execute(...)
            Dim customer = GetCustomer(...)
    
            For Each command In _commands
                customer = command.Execute(customer)
            Next
    
            SaveCustomer(customer)
        End Sub
    
    End Class
    You can add a lot of things here. Sophisticated error tracking, forking chains of commands, etc. But this is the basic flow. You were trying to parallelize it a bit, earlier. There's also a lot of different ways you could do it. But the dang easiest is something like this, that'd execute every step as a separate task:
    Code:
    Public Async Sub Execute(...)
        Dim customer = GetCustomer(...)
    
        For Each command In _commands
            customer = Await command.Execute(customer)
        Next
    
        SaveCustomer(customer)
    End Sub
    I'm still a little shaky on the right syntax. I think to make that work, RelayCommand needs this change:
    Code:
    Public Class AsyncRelayCommand
        
        Private Func(Of Customer, Customer) _execute
    
        Public Sub New(ByVal Func(Of Customer, Customer) execute)
            _execute = execute
        End Sub
    
        Public Function Execute(ByVal customer As Customer) As Task(Of Customer)
            Return Task.Run(Sub() _execute(customer))
        End Function
    End Class
    There's a lot of ways to go from here. The key concept is building classes that represent a process, and a class that executes a chain of them.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  8. #8

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2011
    Location
    England
    Posts
    421

    Re: Task(Of TResult).Factory.ContinueWith with chain of functions, methods and result

    Thank you Sitten Spynne, that explanation and example code was amazing. Don't get me wrong, I had to think it over a bit until something clicked and I was able to comprehend from it what I need to do - but I managed to piece the following test together with the help of your post, Option Strict and a little bit of further googling:
    VB.NET Code:
    1. Private Async Sub TestButton_Click(sender As Object, e As EventArgs) Handles TestButton.Click
    2.   Dim Pipe As New PipeLine
    3.   Pipe.Add(New RelayCommand(AddressOf Test1))
    4.   Pipe.Add(New RelayCommand(AddressOf Test2))
    5.   Pipe.Add(New RelayCommand(AddressOf Test3))
    6.  
    7.   Dim Result As String = Await Pipe.Execute("Testing")
    8.   MessageBox.Show(Result)
    9. End Sub
    10.  
    11. Public Function Test1(Input As String) As String
    12.   Return String.Concat(Input, "-1")
    13. End Function
    14.  
    15. Public Function Test2(Input As String) As String
    16.   Return String.Concat(Input, "-2")
    17. End Function
    18.  
    19. Public Function Test3(Input As String) As String
    20.   Return String.Concat(Input, "-3")
    21. End Function
    22.  
    23. Public Class PipeLine : Inherits CollectionBase
    24.   Public Sub Add(Command As RelayCommand)
    25.     Me.List.Add(Command)
    26.   End Sub
    27.  
    28.   Public Async Function Execute(Param As String) As Task(Of String)
    29.     For Each CMD As RelayCommand In Me.List
    30.       Param = Await CMD.Execute(Param)
    31.     Next CMD
    32.  
    33.     Return Param
    34.   End Function
    35. End Class
    36.  
    37. Public Class RelayCommand
    38.   Private Command As Func(Of String, String)
    39.  
    40.   Public Sub New(CMD As Func(Of String, String))
    41.     Me.Command = CMD
    42.   End Sub
    43.  
    44.   Public Function Execute(Param As String) As Task(Of String)
    45.     Return Task.Run(Function() Me.Command(Param))
    46.   End Function
    47. End Class
    It simply forms the string "Testing-1-2-3".

    I would appreciate you looking it over to verify I have got this right, or if indeed there is perhaps another way that may be better for one reason or another. Although the fact that I get the result I expect from this code tells me it should be fine in practice. Of course, it will need to be adapted for my DLL so that it can raise events back to the control where the functions will reside but I think I have that covered.

    Massive thanks for your help
    Last edited by JayJayson; Feb 16th, 2016 at 08:50 PM.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width