1

I have the same problem like this one Here but using the WebClient class on my client and also the second code example from this answere.
So what can I do to only get one call from my WebClient client?

My httplistener callback gets called twice, the first one is allright but the second one throws this error on at HttpListenerContext context = Listener.EndGetContext(ar);

System.Net.HttpListenerException: 'The I/O operation has been aborted because of either a thread exit or an application request'

Server Code:

private void DoWork(object arg)
    {
        Listener = new HttpListener();
        Listener.Prefixes.Add("https://+:28210");
        Listener.AuthenticationSchemes = AuthenticationSchemes.Basic;
        Console.WriteLine("Listening...");
        Listener.Start();
        Listener.BeginGetContext(ListenerContext, null);
        Console.ReadKey();      
    }

`

 private static void ListenerContext(IAsyncResult ar)
    {
        Console.WriteLine("Get Data...");
        HttpListenerContext context = Listener.EndGetContext(ar);
        HttpListenerRequest request = context.Request;
        HttpListenerResponse response = context.Response;
        HttpListenerBasicIdentity identity = (HttpListenerBasicIdentity)context.User.Identity;
        Listener.BeginGetContext(ListenerContext, null);
        Console.WriteLine("Got Data!");

        //Some more Code...

        byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseData);
        response.ContentLength64 = buffer.Length;
        System.IO.Stream output = response.OutputStream;
        output.Write(buffer, 0, buffer.Length);
    }



Client Code:

using (WebClient client = new WebClient())
            {
                string serialisedData = JsonConvert.SerializeObject(Data);
                client.Credentials = new NetworkCredential(config.UserData.Username, config.UserData.Password);
                byte[] responsebyte = client.UploadData(config.ServerAddress, System.Text.Encoding.UTF8.GetBytes(serialisedData));
                response = System.Text.Encoding.UTF8.GetString(responsebyte);
            }
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
SomePerson
  • 581
  • 1
  • 6
  • 18
  • Questions that don't get answers typically need improvement, not bumping. A *Thread* abort exception has nothing to do with "calling twice" it means there's a threading problem in the code. Perhaps a request wasn't finished by the time the application terminated? Where's the `EndGetContext`, and the rest of the code? – Panagiotis Kanavos Nov 30 '18 at 10:22
  • BTW async/await was introduced to avoid those errors. Instead of `BeginGetContext` use [GetContextAsync](https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener.getcontextasync?view=netframework-4.7.2), eg `var context=await listener.GetContextAsync();` – Panagiotis Kanavos Nov 30 '18 at 10:26
  • The full exception contains the call stack that caused the exception and any inner exceptions. You can get it easily with `Exception.ToString()`. The call stack will show where the problem actually occured and the path that led to it. Post the full exception along with your code – Panagiotis Kanavos Nov 30 '18 at 10:30
  • The exception Comes when `ListenerContext` is called a second time. If I start my Client the `ListenerContext` runs without probelms but strangly it is called a second time after the first one is finished. And `EndGetContext` is on row 2 in `ListenerContext`. The rest of the Code is just simple stuff that happens with the data. I can try to work with `GetContextAsync`. – SomePerson Nov 30 '18 at 10:32
  • and the exception message clearly says that something aborted the operation. Perhaps it was the original HttpListener getting disposed by the GC - DoWork starts a new instance each time. Or the application terminated without *cancelling* the operation. That's why the rest of the code matters. – Panagiotis Kanavos Nov 30 '18 at 10:36
  • Try to get the code working reliably using the *synchronous* methods first. Once that works, use their `...Async` equivalents, *not* the Begin/End methods. Making the example in the [HttpListener](https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener?view=netframework-4.7.2) docs work asynchronously is as easy as changing the return type to `async Task` and replacing `listener.GetContext();` with `await listener.GetContextAsync();`, ` output.Write(` with `await output.WriteAsync. – Panagiotis Kanavos Nov 30 '18 at 10:37
  • The call stack just says it happens at `HttpListenerContext context = Listener.EndGetContext(ar);` like I said. The function worked with the synchronous Methode before. And it kinda works now too but not in the second call. I had my function exactly like in the docs. – SomePerson Nov 30 '18 at 10:41
  • `output.Close` is problematic in case of exceptions. A `using` block should be used to ensure it's closed even if there's an exception : `using(var output = response.OutputStream){await output.WriteAsync(...);}` – Panagiotis Kanavos Nov 30 '18 at 10:42
  • the call stach contains a lot more. Post it here, don't describe parts of it. The code now is *not* equivalent to the docs. The callbacks have changed the flow. The documentation example assumes there's only a single HttpListener instance, called only once. If you wanted to receive multiple requests the code between `listener.Start()` and `listener.Stop()` should be inside a loop. That's why it's important to know who calls `DoWork`? That code wasn't made to be called twice – Panagiotis Kanavos Nov 30 '18 at 10:45
  • `System.Net.HttpListenerException (0x80004005): The I/O operation has been aborted because of either a thread exit or an application request at System.Net.HttpListener.EndGetContext(IAsyncResult asyncResult) at Server.ListenerContext(IAsyncResult ar) in C:\Users\Admin\source\repos\NewProject\Server.cs:line 76` – SomePerson Nov 30 '18 at 10:52

1 Answers1

1

The example in HttpListener's documentation can be used to handle only one call. To handle more calls, the code between listener.Start() and listener.Stop() must run in a loop.

To make this code asynchronous, all that's needed is to use the asynchronous versions of HttpListener.GetContext and Stream.Write:

public static async Task  ListenAsync(params string[] prefixes)
{
    if (prefixes == null || prefixes.Length == 0)
    throw new ArgumentException("prefixes");

    using(var listener = new HttpListener())
    {
        // Add the prefixes.
        foreach (string s in prefixes)
        {
            listener.Prefixes.Add(s);
        }
        listener.Start();
        Console.WriteLine("Listening...");
        for (int i=0;i<3;i++)
        {
            var context = await listener.GetContextAsync();
            Console.WriteLine($"Got {i}");

            var response = context.Response;
            string responseString = $"<HTML><BODY> Hello world {i}!</BODY></HTML>";
            var buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
            response.ContentLength64 = buffer.Length;
            using(var output = response.OutputStream)
            {
                await output.WriteAsync(buffer,0,buffer.Length);
            }
        }
        listener.Stop();
    }
}

ListenAsync must be called only once and awaited until it completes. In this case, it processes up to 3 requests in a loop before exiting.

Calling it in a console application can be as simple as :

static async Task Main(string[] args)
{
    Console.WriteLine("Starting !");
    await ListenAsync(@"http://*:19999/");
    Console.WriteLine("Finished");
}

To stop the listener in a thread-safe manner, a CancellationToken must be used to signal that the listener has to cancel. GetContextAsync() itself can't accept a cancellation token. It can be aborted though by calling HttpListener.Abort. If GetContextAsync() is waiting when that happens, an ObjectDisposedException will be thrown.

The main method waits for a keypress now before signalling for cancellation and waiting for ListenAsync to complete its current request :

static async Task Main(string[] args)
{
    Console.WriteLine("Starting !");
    using(var cts=new CancellationTokenSource())
    {
        try
        {
            var task= ListenAsync(cts.Token, @"http://*:19999/");
            Console.ReadKey();
            cts.Cancel();
            await task;
        }
        catch(ObjectDisposedException)
        {
            Console.WriteLine("Listener aborted");
        }
    }
    Console.WriteLine("Finished");
}

ListenAsync itself uses token.Register(()=>listener.Abort()); on the cancellation token to abort the listener. The for loop changes to while(!token.IsCancellationRequested), allowing the listener to keep listening until a key is pressed :

public static async Task  ListenAsync(CancellationToken token,params string[] prefixes)
{
    if (prefixes == null || prefixes.Length == 0)
    throw new ArgumentException("prefixes");

    using(var listener = new HttpListener())
    {
        foreach (string s in prefixes)
        {
            listener.Prefixes.Add(s);
        }
        listener.Start();
        Console.WriteLine("Listening. Hit any key to end.");

        //Abort if the token is signalled            
        token.Register(()=>listener.Abort());

        int i=0;
        //Loop until cancellation is requested
        while (!token.IsCancellationRequested)
        {
            var context = await listener.GetContextAsync();
            Console.WriteLine($"Got {i++}");

            var response = context.Response;
            string responseString = $"<HTML><BODY> Hello world {i}!</BODY></HTML>";
            var buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
            response.ContentLength64 = buffer.Length;
            using(var output = response.OutputStream)
            {
                await output.WriteAsync(buffer,0,buffer.Length);
            }
        }
        listener.Stop();
    }
}
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236