0

In my WCF service, I have to make a call to an API, where I wanted to do a Fire and Forget implementation. And If possible just capture the errors if any.(That's fine too , if not an option)

I am planning to do the following implementation, what are the issues it could lead to? By doing the following implementation is going to leave a huge number of open connections. Or what could be the issue? Please help in understanding how in a better way this can be implemented.

void SendRequest(inputs)
{
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL);
    request.Method = "POST";
    request.ContentType = "application/xml";

    byte[] requestBytes = Encoding.UTF8.GetBytes(inputXML);
    using (Stream requestStream = request.GetRequestStream())
    {
        requestStream.Write(requestBytes, 0, requestBytes.Length);
    }

    request.GetResponseAsync();
} 
Main()
{ 
    try
        SendRequest(inputs);
    catch ex
        log ex;
}
Zach Pedigo
  • 396
  • 2
  • 9
manas das
  • 64
  • 12
  • You have the power to answer all of those questions yourself. Run you code and see what happens. – gunr2171 Oct 27 '21 at 17:53
  • Also see https://stackoverflow.com/questions/60778423/fire-and-forget-using-task-run-or-just-calling-an-async-method-without-await – gunr2171 Oct 27 '21 at 17:53
  • Thank for the link, I am trying my best to understand. And yes I did try the above one and it seems to be working, But was worried about if there are some issues/Impacts that I may not be aware of. So wanted the experts here to share their thoughts on this approach. – manas das Oct 27 '21 at 19:56

2 Answers2

0

Please note that it's not best practice not to use fire and forget, especially if this a core layer of the application and you might miss important exceptions. When you use this technique you have to remember that the following happens:

  1. Exception will be fail silently without any chance of catching them. normally you will want to log them or get a notification.
  2. You have no idea when the code completes,
  3. Since You don't need the code to complete and it might may not run to you would have no notification that it failed to complete.

A good case scenario for using this technique could be for updating a cache for an example.

Having said that, you could use the following techniques:

NET 4.5 allows us to use it via Task.Run

Task.Run(() => FireAndForget());

You could also start a thread with parameterless lambda:

(new Thread(() => { 
 FireAndForget(); 
 }) { 
   Name = "Running Work Thread (FireAndForget)",
   Priority = ThreadPriority.BelowNormal 
}).Start();
Ran Turner
  • 14,906
  • 5
  • 47
  • 53
  • I will try this too to understand all the different approaches that can be taken to achieve the same behavior. – manas das Oct 28 '21 at 06:40
0

First, make fully async version of your code

using System.Threading;

public async Task<System.Net.WebResponse> SendRequestAsync(
    string inputXML, string url, CancellationToken cancellationToken)
{
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
    request.Method = "POST";
    request.ContentType = "application/xml";

    byte[] requestBytes = Encoding.UTF8.GetBytes(inputXML);

    // GetRequestStreamAsync is a lightweight operation I assume
    // so we don't really need any cancellation
    using (Stream requestStream = await request.GetRequestStreamAsync())
    {
        // Idk if we really need cancellation here, but just in case of big request we might need to
        await requestStream.WriteAsync(
            requestBytes, 0, requestBytes.Length, cancellationToken);
    }
    
    // Close any long-running request
    using (cancellationToken.Register(() => request.Abort(), useSynchronizationContext: false))
    {
        var response = await request.GetResponseAsync();
        cancellationToken.ThrowIfCancellationRequested();
        return response;
    }
} 

Let's create an async void method, but make it safe. It will basically execute in "fire-and-forget" manner.

public async void DoWork(string inputXML, string url, CancellationToken ct)
{
    try
    {
        using(var response = await SendRequestAsync(inputXML, url, ct))
        {
            var httpResponse = (HttpWebResponse) response;
            // Use 201 Created or whatever you need
            if (httpResponse.StatusCode != HttpStatusCode.Created)
            { 
                // TODO: handle wrong status code
            }
        }
    }
    catch (Exception e)
    {
        if (ct.IsCancellationRequested)
        {
            Console.WriteLine("Cancelled");
        }
        else
        {
            // TODO: handle exception
        }
    }
}

private static CancellationTokenSource _cts = new CancellationTokenSource();

public static void Main (string[] args)
{
    DoWork("xml string", "example.com", cts.Token);
    Console.WriteLine("Boom!");
    if (Console.ReadLine() == "exit")
    {
        // Cancel the damn job
        cts.Cancel();
    }
}

It's important to handle all errors from inside a DoWork, because following will not work

// Warning, will NOT catch an exception
public static void Main (string[] args)
{
    try 
    {
        DoWork("xml string", "example.com"); 
    }
    catch (Exception e)
    {
    }
}

EDIT: OP requested cancellation so I added cancellation

Andriy Shevchenko
  • 1,117
  • 6
  • 21
  • All these changes make sense, I am giving it a try now and will update you on the status. Just wanted to know that even if the calling thread will be closed this still will work. e.g My WCF service got called which invoked an API through the above-suggested approach, and let's assume that the API is going to take 2 min. So in this scenario, the API call won't be terminated right even if the Calling thread will get closed just after this method is called – manas das Oct 27 '21 at 20:40
  • @manasdas excellent question. Will add cancellation ability to my answer – Andriy Shevchenko Oct 27 '21 at 21:18
  • Thank you. So If I decide that, I don't have to cancel the API call even if my Calling method (The Operation that has invoked the WCF service) is completed. Then I can use the previous version of your suggestion too? My apologies for so many questions, I just want to understand it a better way, and these questions come up during testing with different scenarios. – manas das Oct 28 '21 at 06:45
  • In case you want to do nothing at all pass `CancellationToken.None` to `DoWork`, if you want to cancel it somewhere manually call `cts.Cancel()`. API call will run to the end with two scenarios: you pass `CancellationToken.None` or just don't cancel. – Andriy Shevchenko Oct 28 '21 at 08:37
  • Things worked without the cancellation implementation, Just that the exception in case of like API URL is not able to resolve or something ..those are not getting captured. Any thoughts on that – manas das Oct 28 '21 at 09:25
  • @manas das it will not propagate to `Main`. You need to handle it manually in DoWork. Or you mean that DoWork didn't catch an Exception? – Andriy Shevchenko Oct 28 '21 at 09:44
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/238646/discussion-between-manas-das-and-andriy-shevchenko). – manas das Oct 28 '21 at 10:02
  • correct the DoWork() did not capture the exception. – manas das Oct 28 '21 at 10:17