Threading

Hitoruna

Member
Joined
Sep 12, 2017
Messages
11
Programming Experience
5-10
I am reading a lot about threading and asynchronous processing in C#, and I am getting dizzy already. Apparently there many ways to do this and I don't know what is the difference.
So far I get (there are more apparently)

  1. The old Thread class
  2. The asynchronous invocation with BeginInvoke and EndInvoke
  3. The async-await way

Can someone explain me what the differences are (Also some authors suggest that #3 does not actually create a different thread)

Also more importantly, I am trying to understand some advance code I got that uses asynchronous processing and callbacks a lot so a punctual question:

How can I know in which thread some operation is running? ( I read somewhere but I can't find it :( the way to do this. Something about Thread.someoperationhere? I think but I am not sure)
 
Microsoft have added different multi-threading functionality to the .NET Framework and its associated languages over time. Originally, you would generally create a Thread object directly if you wanted to do some background work or use the ThreadPool class if you wanted to queue multiple background tasks. BeginInvoke/EndInvoke were basically for building an asynchronous API, i.e. you could create types that encapsulated asynchronous functionality and the consumer could then use fairly easily without having to do too much work. Later, they introduced the Tasks Parallel Library that provided more functionality with a simpler interface and now async/await is an even easier way to use the TPL.

In short, you should use async/await in your own code unless you have a specific reason to do otherwise. The most common reason to do otherwise would be that you're making use of an existing asynchronous API that does not itself use async/await. Mind you, Microsoft has added async/await versions of an asynchronous API to many classes that already had asynchronous APIs implemented using a different mechanism.

In simple terms, an async method will return a Task object, which is basically a method that is executed in the background. It may or may not be executed on a different thread but that's up to the TPL. You don't need to think in terms of threads but rather just foreground and background. When you call an async method, you can either await it or not. If you don't, the method will return immediately and you get a Task back. Your code will continue to execute in the foreground and the method inside that Task will continue to complete in the background. You won't be able to use the result of the background method until it completes, at which point you can get its result from the Task.

An example of a situtation where you might not await when you call an async method is if you have multiple independent background tasks to perform. If you were to await each one individually then they would be executed serially, with each one waiting for the previous one to complete before it began. Instead, you could start them all serially and not await each one and thus they will then all execute in parallel. After you've started each one you can await them all as a group, so you code will not continue until you have all the results.

If you do call an async method and await it then your code will wait at that call until the task completes. This is somewhat like a synchronous call but the difference is that, rather than blocking everything else, the await keyword will cause control to jump back out to the previous caller so that it can continue doing other foreground work while that background work is going on. What this does is ensure that the maximum amount of foreground and background work can be done in parallel. As long as you have an await before the use of any data retrieved asynchronously, you ensure that there will not be an attempt to use data that is still in the process of being retrieved.

Take a look at this code with all synchronous methods:
public void Method1()
{
    var data = Method2();

    // work1A
    // work1B
    // work1C

    // Use data here.
}

public object Method2()
{
    var result = Method3();

    // work2A
    // work2B
    // work2C

    return result;
}

public object Method3()
{
    object result;

    // work3A
    // work3B
    // work3C

    return result;
}

If you were to call Method1 in that code then it will call Method2 and block, meaning that work1A, work1B and work1C will not even be attempted until Method2 completes. In Method2, it will call Method3 and block until it completes, meaning that Method3 must complete before work2A, work2B and work2C will even be attempted. That means that work3A, work3B and work3C must complete before work2A, work2B and work2C will be begun and they must complete before work1A, work1B and work1C will be begun.

Now take a look at this analogous code that uses async/await:
public async Task Method1()
{
    var dataTask = Method2();

    // work1A
    // work1B
    // work1C

    var data = await dataTask;

    // Use data here.
}

public async Task<object> Method2()
{
    var resultTask = Method3();

    // work2A
    // work2B
    // work2C

    return await resultTask;
}

public async Task<object> Method3()
{
    return await Task.Run(() =>
                          {
                              object result;

                              // work3A
                              // work3B
                              // work3C

                              return result;
                          });
}

In this case, Method1 calls Method2 and simply continues on to execute work1A, work1B and work1C while Method2 is executed in the background. Likewise, Method2 calls Method3 and simply continues on to execute work2A, work2B and work2C while Method3 is executed in the background. This means that you can have three sets of work going on simultaneously, thus making the best use of system resources and not making tasks in your application wait for other tasks that they are not actually dependent on.

In Method1, the code awaits the Task that is executing Method2 only when it needs to, i.e. immediately before it actually uses the data returned by Method2. By the time Method1 gets to that point, Method2 may have already completed and so Method1 can continue immediately, so it will not have been delayed at all. If Method2 hasn't yet completed then Method1 will wait until it has. At least it has managed to get some other work done in the meantime though, rather than still having to do that work after Method2 completes as before using the data returned.

Likewise, Method2 will only wait for Method3 to complete when it has to, i.e. before it returns. When it gets to that point, Method3 may already have completed and thus Method2 can return immediately but, if Method3 is still executing, at least Method2 has been able to get some work done in the meantime and doesn't still have that work to do once Method3 completes.

So, when using async/await, you should await an async method as late as you can. If you need to use the result immediately then await the call and you will get the result back directly, e.g.
var result = await SomeMethodAsync();

// Use result here.

If you don't need to use the result immediately then don't await the call and get a Task back, then await the Task before using the result later, e.g.
var resultTask = SomeMethodAsync();

// Do some more work here.

var result = await resultTask;

// Use result here.
 
Note that, in the second code snippet above, you use the Task.Run method to create a Task to await in an async method in which you aren't actually calling any async methods.
 

Latest posts

Back
Top Bottom