6

We need to write some logs in our code.
The logs are being written via a network request which logs some info (the logger is an Http-API module), But we don't want to break the whole flow if there's an exception in the API.

I don't to use Task.Run for this kind of operation, because it's bad.

But then I thought about non-awaitable-inline-function , something like this :

private static readonly HttpClient client = new HttpClient();

void Main()
{

    Console.WriteLine(1);
    async Task LogMe()
    {
        var response = await client.PostAsync("http://www.NOTEXISTS.com/recepticle.aspx", null);
        var responseString = await response.Content.ReadAsStringAsync();
        //response.EnsureSuccessStatusCode(); // I don't need this here
        Console.WriteLine("error");
    }

    //some code
    LogMe();
    //some code
    Console.WriteLine(3);

}

output:

1
2

Question:

Is it a viable solution for logging in a "fire-and-forget" mode?
What if I create objects inside this inline method, when are they going to be GC'ed?

Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • personally I use ` _ = task.ContinueWith(c => { var ignore = c.Exception; });` so it is calling the function and continue even it throws exception. – Saif Dec 26 '19 at 08:09
  • @Saif It's similar to Task.Run....No need to use a thread for this – Royi Namir Dec 26 '19 at 08:12
  • 1
    Why is 'Task.Run' bad? – Johan Donne Dec 26 '19 at 08:20
  • 1
    Regardless of whether Task.Run is bad, using it without an `await` would have the same result as the current code. – GSerg Dec 26 '19 at 08:20
  • 1
    If you're wondering whether a fire and forget will allow you to ignore exceptions and is otherwise a recognized option, [then yes](https://stackoverflow.com/q/46053175/11683). The GC should not be of any concern because all that happens inside managed code (without unmanaged callbacks) should be properly GC'ed, not earlier than possible. – GSerg Dec 26 '19 at 08:24
  • 2
    "Is it a viable solution for logging in a "fire-and-forget" mode?" Does the code do what you expect it to do? Is it readable, maintainable, doesn't introduce new risks to the system? if the answer to all of these questions is yes, then yes, it's a viable solution. "What if I create objects inside this inline method, when are they going to be GC'ed?" Why do you care? The GC is invoked by the CLR based on it's inner algorithm - the entire concept of managed memory is that the runtime manages memory for you, and you shouldn't care about memory management (of course, there are exceptions...) – Zohar Peled Dec 26 '19 at 08:31
  • 1
    `But then I thought about non-awaitable-inline-function , something like this`, then you go ahead and use `await, async`? You're contradicting yourself. Anyhow, fire and forget logging is pretty common. If you create unmanaged objects or use objects which implement `IDisposable`, make sure you dispose them properly. Disposal or GC doesn't and shouldn't depend on fire and forget scenarios; the rules stay the same. – CodingYoshi Dec 26 '19 at 08:53
  • `Task.Run` [is not bad](https://stackoverflow.com/questions/38739403/await-task-run-vs-await-c-sharp/58306020#58306020). Blocking code is bad. `Task.Run` does not block be itself. If you use it to run sync code then it will block a thread. If you use it to run async code then it will not block a thread. – Theodor Zoulias Dec 26 '19 at 15:27

2 Answers2

5

I don't to use Task.Run for this kind of operation, because it's bad.

It's important to recognize/specify context for this kind of statement. Task.Run is bad on ASP.NET. It's perfectly OK to use in a GUI app on the client side.

We need to write some logs in our code.

I strongly recommend using an established logging library. Most of them work by using an in-memory queue that is (synchronously) written to by your code, and which is continually processed by a background thread. This is a pattern that is well-established.

Is it a viable solution for logging in a "fire-and-forget" mode?

One of the problems with "fire and forget" is that you don't know when there are errors. The established logging libraries all have some kind of system for dealing with errors communicating with the logging backend; the code you posted will just ignore them.

What if I create objects inside this inline method, when are they going to be GC'ed?

Conceptually, async methods act as GC "roots" as long as they are going to continue executing in the future. So local objects will be GC'ed when the method completes.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
4

I don't to use Task.Run for this kind of operation, because it's bad.

It's not bad at all. It's just another tool in your tool belt.

It's important to understand how asynchronous methods work. Asynchronous methods all start running synchronously, just like any other method. The magic happens at the first await that acts on an incomplete Task. At that point, await sees the incomplete Task and the method returns. Usually it returns its own incomplete Task, unless the method is async void, then it returns nothing.

Asynchronous is not about how methods run. It's about how methods wait.

So in your code, LogMe() will start running on the same thread. Only when you are waiting for a response will a Task be returned up the call stack, and you're free to await it or not.

The time it takes to setup the request and send it isn't that much. So you won't notice it. But if you were doing something that did do some CPU-intensive work before the request, then it would be appropriate to use Task.Run, since that tells it to start on a different thread, so it doesn't hold you up.

If this was ASP.NET (not Core), then you may benefit from either using Task.Run, or using .ConfigureAwait(false) on the PostAsync, because otherwise it will try to come back to the same synchronization context (the context of the current incoming HTTP request), which means the current request cannot complete before LogMe() completes.

However, if you are going to fire and forget then keep in mind that whatever started the operation will have no idea if it succeeds. You should use try/catch inside LogMe if you want to log failures somewhere else (a text file or something). PostAsync will throw an exception if there was a problem sending the request (DNS lookup failure, no response, etc).

Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84