我如何使用方法async Task <string>并返回字符串,然后如何将该方法的委托传递给构造函数并在以后使用它?

时间:2015-11-01 11:51:08

标签: c# .net winforms async-await

我有这个返回string的方法:

public string SendResponse(HttpListenerRequest request)
{
    string result = "";
    string key = request.QueryString.GetKey(0);
    if (key == "cmd")
    {
        if (request.QueryString[0] == "uploadstatus")
        {
            switch (Youtube_Uploader.uploadstatus)
            {
                case "uploading file":
                    return "uploading " + Youtube_Uploader.fileuploadpercentages;

                case "status":
                    return Youtube_Uploader.fileuploadpercentages.ToString();

                case "file uploaded successfully":
                    Youtube_Uploader.uploadstatus = "";
                    return "upload completed," + Youtube_Uploader.fileuploadpercentages + ","
                       + Youtube_Uploader.time;
                default:
                    return "upload unknown state";
            }


        }
        if (request.QueryString[0] == "nothing")
        {
            return "Connection Success";
        }
        if (request.QueryString[0] == "start")
        {
            StartRecrod();
            result = "Recording started";
        }

        if (request.QueryString[0] == "stop")
        {
            dirchanged = false;
            StartRecrod();
            result = "Recording stopped and preparing the file to be shared on youtube";
            string fileforupload = await WatchDirectory();
            await WaitForUnlockedFile(fileforupload);
            using (StreamWriter w = new StreamWriter(userVideosDirectory + "\\UploadedVideoFiles.txt", true))
            {
                w.WriteLine(fileforupload);
            }
            uploadedFilesList.Add(fileforupload);
            Youtube_Uploader youtubeupload = new Youtube_Uploader(uploadedFilesList[0]);
        }
    }
    else
    {
        result = "Nothing have been done";
    }

    return result;

}

问题在于,在这个方法中,我使用了两行await

string fileforupload = await WatchDirectory();
await WaitForUnlockedFile(fileforupload);

在这两行上出错。两个错误都是一样的:

  

错误'await'运算符只能在异步方法中使用。   考虑使用'async'修饰符标记此方法并进行更改   它的返回类型为'Task<string>'

问题是否可以使SendResponse()方法返回字符串,因为我需要它并使用await

这就是我需要在await方法中使用SendResponse()的两种方法:

private async Task<string> WatchDirectory()
{
    using (FileSystemWatcher watcher = new FileSystemWatcher())
    {
        TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();

        watcher.Path = userVideosDirectory;
        watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size;
        watcher.Filter = "*.mp4";
        watcher.Changed += (sender, e) => tcs.SetResult(e.FullPath);
        watcher.EnableRaisingEvents = true;

        return await tcs.Task;
    }
}

// You can get rid of the OnChanged() method altogether

private async Task WaitForUnlockedFile(string fileName)
{
    while (true)
    {
        try
        {
            using (IDisposable stream = File.Open(fileName, FileMode.OpenOrCreate,
                FileAccess.ReadWrite, FileShare.None))
            { /* on success, immediately dispose object */ }

            break;
        }
        catch (IOException)
        {
            // ignore exception
            // NOTE: for best results, consider checking the hresult value of
            // the exception, to ensure that you are only ignoring the access violation
            // exception you're expecting, rather than other exceptions, like
            // FileNotFoundException, etc. which could result in a hung process
        }

        // You might want to consider a longer delay...maybe on the order of
        // a second or two at least.
        await Task.Delay(100);
    }
}

更新

我将方法SendResponse()更改为async Task<string> 但是在WebServer类构造函数中,我有这个并且在这一行上得到错误:

WebServer ws = new WebServer(SendResponseAsync, "http://+:8098/");

(SendResponseAsync是SendResponse更名)

错误是:

  

错误1'System.Threading.Tasks.Task Automatic_Record.Form1.SendResponseAync(System.Net.HttpListenerRequest)'的返回类型错误

WebServer类是:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Threading;

namespace Automatic_Record
{
    class WebServer
    {
        private readonly HttpListener _listener = new HttpListener();
        private readonly Func<HttpListenerRequest, string> _responderMethod;

        public WebServer(string[] prefixes, Func<HttpListenerRequest, 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, string> method, params string[] prefixes)
            : this(prefixes, method) { }

        public void Run()
        {
            ThreadPool.QueueUserWorkItem((o) =>
            {
                Console.WriteLine("Webserver running...");
                try
                {
                    while (_listener.IsListening)
                    {
                        ThreadPool.QueueUserWorkItem((c) =>
                        {
                            var ctx = c as HttpListenerContext;
                            try
                            {
                                string rstr = _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();
        }
    }
}

更新2

我尝试了peter解决方案,所以我将WebServer类代码更改为此问题解决方案中的Peter显示。

然后在form1构造函数中我做了:

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

然后方法SendResponseAsync:

public async Task<string> SendResponseAsync(HttpListenerRequest request)
        {
            string result = "";
            string key = request.QueryString.GetKey(0);
            if (key == "cmd")
            {
                if (request.QueryString[0] == "uploadstatus")
                {
                    switch (Youtube_Uploader.uploadstatus)
                    {
                        case "uploading file":
                            return "uploading " + Youtube_Uploader.fileuploadpercentages;

                        case "status":
                            return Youtube_Uploader.fileuploadpercentages.ToString();

                        case "file uploaded successfully":
                            Youtube_Uploader.uploadstatus = "";

                            return "upload completed," + Youtube_Uploader.fileuploadpercentages + ","
                               + Youtube_Uploader.time;

                        default:
                            return "upload unknown state";
                    }


                }
                if (request.QueryString[0] == "nothing")
                {

                    return "Connection Success";
                }
                if (request.QueryString[0] == "start")
                {
                    StartRecrod();
                    result = "Recording started";
                }


                if (request.QueryString[0] == "stop")
                {
                    dirchanged = false;
                    StartRecrod();
                    result = "Recording stopped and preparing the file to be shared on youtube";
                    string fileforupload = await WatchDirectory();
                    await WaitForUnlockedFile(fileforupload);
                    using (StreamWriter w = new StreamWriter(userVideosDirectory + "\\UploadedVideoFiles.txt", true))
                    {
                        w.WriteLine(fileforupload);
                    }
                    uploadedFilesList.Add(fileforupload);
                    Youtube_Uploader youtubeupload = new Youtube_Uploader(uploadedFilesList[0]);
                }


            }
            else
            {
                result = "Nothing have been done";
            }

            return result;

        }

WatchDirectory:

private async Task<string> WatchDirectory()
        {
            using (FileSystemWatcher watcher = new FileSystemWatcher())
            {
                TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();

                watcher.Path = userVideosDirectory;
                watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size;
                watcher.Filter = "*.mp4";
                watcher.Changed += (sender, e) => tcs.SetResult(e.FullPath);
                watcher.EnableRaisingEvents = true;

                return await tcs.Task;
            }
        }

并持续WaitForUnlockedFile

private async Task WaitForUnlockedFile(string fileName)
        {
            while (true)
            {
                try
                {
                    using (IDisposable stream = File.Open(fileName, FileMode.OpenOrCreate,
                        FileAccess.ReadWrite, FileShare.None))
                    { /* on success, immediately dispose object */ }

                    break;
                }
                catch (IOException)
                {
                    // ignore exception
                    // NOTE: for best results, consider checking the hresult value of
                    // the exception, to ensure that you are only ignoring the access violation
                    // exception you're expecting, rather than other exceptions, like
                    // FileNotFoundException, etc. which could result in a hung process
                }

                // You might want to consider a longer delay...maybe on the order of
                // a second or two at least.
                await Task.Delay(100);
            }
    }

但在线上出错:

Task.Run 严重级代码说明项目文件行错误无法将lambda表达式转换为类型'string []',因为它不是委托类型Automatic_Record

还有“http://+:8098/”行的错误 严重级代码描述项目文件行错误参数2:无法从'string'转换为'System.Func&gt;

4 个答案:

答案 0 :(得分:5)

  

是否可以使SendResponse返回的问题   字符串,因为我需要它,也使用await?

异步是“一直”。这意味着一旦在代码中开始使用await,您的方法签名就会向上传播,遍历整个调用堆栈。 这意味着代码中的任何async方法都必须返回TaskTask<T>并添加async修饰符,以便编译器按顺序检测到这是一个异步方法,需要转换为状态机。

这意味着这个同步签名:

public string SendResponse(HttpListenerRequest request)

需要变成异步签名:

public async Task<string> SendResponseAync(HttpListenerRequest request)

使用Task.Result同步阻止代码的选项。我不推荐它,如you shouldn't block on async code。这只会导致麻烦(通常是死锁)。

答案 1 :(得分:2)

正如answerer Yuval所说(并且正如我对前一个问题的回答所说),一旦从async开始,它通常必须一直向上传播调用堆栈。也就是说,有其他选择:

  • 正如我在上一篇回答中提到的,您可以同步等待异步操作。不理想,实际上只是一个权宜之计,直到你可以进一步改进代码,但它可以工作。
  • 您可以捕获异步操作(即Task)并稍后再使用。

在您的具体示例中,第二个选项应该可以正常工作。即您需要修复的第一件事就是调整构造函数,以便它可以接受异步方法。然后你可以稍后调用该方法,最好是异步调用。

例如:

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();
    }
}


当然,使用更改的构造函数,还必须更改任何其他调用者(如果存在)。理想情况下,您可以更改与这些调用者相关的代码,以便它也遵循async模型,充分利用并获得该方法的全部好处。

但是,如果您不能或不会这样做,可以将旧的同步代码调整为异步模型。例如。如果您有这样的事情:

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

...你可以把它改成这样的东西:

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

您也可以创建构造函数重载来为任何此类调用者包装同步方法,以便原始调用站点可以保持不变:

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

请注意,通过跟随async一直向上调用堆栈,代码中没有任何操作必须在某个异步事件或操作上等待一段时间的位置将导致代码阻止。相反,它们只是将控制权返回给执行线程,允许框架在异步事件或操作发生或完成时,以后继续执行async方法。

如果你试图通过简单地等待异步操作完成来“解决”问题,你会(在这种特殊情况下)绑定一个线程池线程,阻塞它直到它可以继续。这样,一旦异步操作启动,线程池线程就会返回到池中,并且程序将不再需要为该操作再次使用线程池线程,直到操作本身实际完成为止。


顺便说一句:作为一般规则,不建议使用代码来忽略可能发生的每个异常。您应该只捕获那些您希望发生的异常以及您确定可以安全忽略的异常。即便如此,您至少应该以某种方式报告它们以帮助您或者用户能够诊断代码中不存在的问题。其他异常很容易使程序的执行处于损坏状态。充其量,这些问题将非常难以诊断和修复,最坏的情况是最终可能会破坏用户状态,输出数据等。

答案 2 :(得分:1)

SendResponse声明为Task<string>。这表示此任务将返回一个字符串。

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

无论何时你打电话:

string result = await SendRespone(request);

答案 3 :(得分:0)

我相信您最新的编译错误是由于没有为 WebServer 类提供具有正确签名的lambda。因此,您需要更改传递构造函数的内容或预期内容。通过实施 WebServer 的方式,它需要 _responderMethod 的非异步方法。因此,尝试使 SendResponse()异步会产生反效果。我强烈建议重构 WebServer 以异步方式处理响应,或者返回使 SendResponse()非异步。

如果您希望按原样编译代码,可以尝试这样的方法:

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

您可能还必须将lambda包装在正确的委托类型中,如下所示:

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