How can I use a method async Task and return string, and then how do I pass a delegate for that method to a constructor and use it later?

前端 未结 4 525
滥情空心
滥情空心 2021-01-20 15:05

I have this method that returns string:

public string SendResponse(HttpListenerRequest request)
{
    string result = \"\";
    string key = req         


        
相关标签:
4条回答
  • 2021-01-20 15:27

    The question if it's possible to make the SendResponse to return string since i need it and also to use the await ?

    Async is "all the way". It means that once you start using await in your code, your method signature propagates upwards, going through your entire call-stack. This means that any async method in your code has to return either a Task or a Task<T> and have the async modifier added to it, in-order for the compiler to detect that this is an async-method and needs to be transformed into a state-machine.

    What this means is that this synchronous signature:

    public string SendResponse(HttpListenerRequest request)
    

    Needs to turn into an asynchronous signature:

    public async Task<string> SendResponseAync(HttpListenerRequest request)
    

    There is an option of synchronously blocking on your code using Task.Result. I would not recommend it, as you shouldn't block on async code. This leads to nothing more than trouble (usually deadlocks).

    0 讨论(0)
  • 2021-01-20 15:32

    Declare SendResponse as a Task<string>. This says that this Task will return a string.

    public async Task<string> SendResponse(HttpListenerRequest request)
    {
        ...
    }
    

    And in whenever you're calling it:

    string result = await SendRespone(request);
    
    0 讨论(0)
  • 2021-01-20 15:37

    As answerer Yuval says (and as my answer to your previous question said), once you start with async, it generally has to propagate all the way up the call stack. That said, there are alternatives:

    • As I mentioned in my previous answer, you can synchronously wait on asynchronous operations. Not ideal, really just a stop-gap measure until you can improve the code further, but it can work.
    • You can capture the asynchronous operation (i.e. the Task) and use that later.

    In your specific example, the second option should work just fine. I.e. the first thing you need to fix is simply to adjust your constructor so that it can accept the asynchronous method. Then you can call that method later, preferably asynchronously.

    For example:

    class WebServer
    {
        private readonly HttpListener _listener = new HttpListener();
        private readonly Func<HttpListenerRequest, Task<string>> _responderMethod;
    
        public WebServer(string[] prefixes, Func<HttpListenerRequest, Task<string>> method)
        {
            if (!HttpListener.IsSupported)
                throw new NotSupportedException(
                    "Needs Windows XP SP2, Server 2003 or later.");
    
            // URI prefixes are required, for example 
            // "http://localhost:8080/index/".
            if (prefixes == null || prefixes.Length == 0)
                throw new ArgumentException("prefixes");
    
            // A responder method is required
            if (method == null)
                throw new ArgumentException("method");
    
            foreach (string s in prefixes)
                _listener.Prefixes.Add(s);
    
            _responderMethod = method;
            _listener.Start();
        }
    
        public WebServer(Func<HttpListenerRequest, Task<string>> method, params string[] prefixes)
            : this(prefixes, method) { }
    
        public void Run()
        {
            ThreadPool.QueueUserWorkItem((o) =>
            {
                Console.WriteLine("Webserver running...");
                try
                {
                    while (_listener.IsListening)
                    {
                        ThreadPool.QueueUserWorkItem(async (c) =>
                        {
                            var ctx = c as HttpListenerContext;
                            try
                            {
                                string rstr = await _responderMethod(ctx.Request);
                                System.Diagnostics.Trace.Write(ctx.Request.QueryString);
                                //ctx.Request.QueryString
    
                                byte[] buf = Encoding.UTF8.GetBytes(rstr);
                                ctx.Response.ContentLength64 = buf.Length;
                                ctx.Response.OutputStream.Write(buf, 0, buf.Length);
                                System.Data.SqlClient.SqlConnectionStringBuilder builder = new System.Data.SqlClient.SqlConnectionStringBuilder();
    
                            }
                            catch { } // suppress any exceptions
                            finally
                            {
                                // always close the stream
                                ctx.Response.OutputStream.Close();
                            }
                        }, _listener.GetContext());
                    }
                }
                catch { } // suppress any exceptions
            });
        }
    
        public void Stop()
        {
            _listener.Stop();
            _listener.Close();
        }
    }
    


    Of course, with the changed constructor, any other callers (if any exist) will also have to be changed. Ideally, you would go change the code related to those callers so that it also follows the async model, taking full advantage and gaining the full benefits of that approach.

    But again, if you cannot or will not do that, it is possible to adapt older synchronous code to the asynchronous model. E.g. if you have something like this:

    var server = new WebServer(SomeOtherSendResponse, "http://+:8098/");
    

    …you could change it to something like this:

    var server = new WebServer(
        request => Task.Run(() => SomeOtherSendResponse(request)),
        "http://+:8098/");
    

    You could alternatively create a constructor overload to wrap the synchronous method for any such callers, so that the original call site can remain the same:

    public WebServer(Func<HttpListenerRequest, string> method, params string[] prefixes)
        : this(request => Task.Run(() => method(request)), prefixes)
    { }
    

    Note that by following async all the way up the call stack, none of the places in the code where the operation would have to wait for an extended period of time on some asynchronous event or action will cause the code to block. Instead, they simply return control to the executing thread, allowing the framework to continue execution of the async method later if and when the asynchronous event or action occurs or completes.

    Were you to attempt to "solve" the problem by simply waiting synchronously for the asynchronous operation to complete, you would (in this particular case) tie up a thread pool thread, blocking it until it could proceed. This way, the thread pool thread is returned to the pool as soon as the asynchronous operation is started and your program won't require the use of a thread pool thread again for that operation until the operation itself actually completes.


    As an aside: it is not recommended as a general rule to have code that will ignore literally every exception that could occur. You should catch only those exceptions that you expect to occur and which you know for sure are safe to ignore. Even there, you should at least report them somehow to aid in yours or a user's ability to diagnose problems that are not in your code. Other exceptions can easily leave your program's execution in a corrupt state. At best, those kinds of problems will be extremely difficult to diagnose and fix and at worst you could wind up corrupting user state, output data, etc.

    0 讨论(0)
  • 2021-01-20 15:44

    I believe your latest compile errors are due to not providing a lambda with the correct signature to the WebServer class. So you need to either change what you're passing the constructor, or what it expects. With the way you've implemented WebServer, it needs a non-asynchronous method for _responderMethod. Thus trying to make SendResponse() async is counter productive. I would strongly suggest refactoring either WebServer to process responses asynchronously, or go back to making SendResponse() non-async.

    If you'd like to get your code to compile as is you could try something like this:

    var ws = new WebServer(
            request =>
               {
                  var t = SendResponseAsync(request);
                  t.Wait();
                  return t.Result;
               },
            "http://+:8098/");
            ws.Run();
    

    You might also have to wrap the lambda in the correct delegate type like so:

    var ws = new WebServer(
            new Func<HttpListenerRequest, string>(request =>
               {
                  var t = SendResponseAsync(request);
                  t.Wait();
                  return t.Result;
               }),
            "http://+:8098/");
            ws.Run();
    
    0 讨论(0)
提交回复
热议问题