【问题标题】:How can i make that the method will return first a string and then will continue the rest of the code?我怎样才能使该方法首先返回一个字符串,然后继续其余代码?
【发布时间】:2015-11-15 08:37:36
【问题描述】:

我有一个网络服务器,我添加了这个方法:

public async Task<string> SendResponseAsync(HttpListenerRequest request)//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 = "";
                            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);
                    uploadedFilesList.Add(fileforupload);
                    Youtube_Uploader youtubeupload = new Youtube_Uploader(fileforupload);//uploadedFilesList[0]);
                }
            }
            else
            {
                result = "Nothing have been done";
            }
            return result;
        }

问题出在这部分:

result = "Recording stopped and preparing the file to be shared on youtube";
string fileforupload = await WatchDirectory();
await WaitForUnlockedFile(fileforupload);

问题是它不会返回结果,直到它完成等待。 但我需要以某种方式使其首先返回结果,然后再返回结果。

这是 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)
                {

                }
                await Task.Delay(100);
            }
        }

更新:

这就是我在 form1 构造函数中为 WebServer 创建实例并使用方法 SendResponseAsync 的方式。

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

这是 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, 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 (Exception error)
                            {

                                string ttt = error.ToString();
                            } // 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();
        }
    }

【问题讨论】:

  • 你尝试类似'System.Threading.Thread.Sleep(5000);' ?
  • 完全不清楚您要在这里做什么 - WatchDirectoryWaitForUnlockedFile 做什么?您希望如何在方法完成之前使用它的结果?您需要更彻底地解释这一点。
  • 蚂蚁我会解释。这个方法是我在 c# 上的 web 服务器的一部分,我正在使用这个方法从我的 java 程序客户端获取命令。客户端每秒发送一次命令,我在方法中检查它并根据情况向客户端返回一个字符串。在这种情况下,“停止”就像在“开始”等其他情况下一样,我在 java 端向客户端返回一个字符串,在 java 端我使用 texttospeech 来说出字符串。在这种情况下,它应该说:录制停止并准备要在 youtube 上共享的文件问题是 AWAIT 行使其保持不变并且仅在等待之后。
  • 只有在等待完成后,它才会发送字符串“录制停止并准备要在 youtube 上共享的文件”。问题是这个等待需要一些时间。这可能需要一秒钟,有时甚至需要 30 秒或更长时间。等待做什么?在 WatchDirectory 和 WaitForUblockedFile 中,我正在等待写入文件。有时文件可能是 1kb,有时是 1GB。所以我需要找到一种方法,首先将此字符串返回给客户端,然后通过逻辑等待。他们都应该在“停止”IF内。
  • Ant 我也用这两种缺失的方法更新了我的问题。

标签: c# .net winforms


【解决方案1】:

如果我正确理解了这个问题,在SendResponseAsync() 方法的操作过程中您有多个“检查点”,并且您希望能够向客户端发送带有一些状态消息的响应(例如,您想要在对WatchDirectory() 的调用完成后,但在执行对WaitForUnlockedFile() 的调用之前报告结果"Recording stopped and preparing the file to be shared on youtube"

基本问题是SendResponseAsync() 方法只能返回单个值,就像任何其他方法async 或其他方法一样。您不能让任何方法返回超过一次,这对于 async 方法和任何其他类型一样正确。

那么,该怎么办?好吧,如果没有在上下文中看到代码,就不可能肯定地说(即没有a good, minimal, complete code example 可以可靠地重现您的问题)。但是 C# 中的一种惯用方法是使用 IProgress&lt;T&gt; 接口,以允许使用状态值回调方法的调用者,以便它可以适当地处理它们(例如,通过将它们发送到客户端)。

例如,您可以将方法更改为如下所示:

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

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

                    case "file uploaded successfully":
                        Youtube_Uploader.uploadstatus = "";
                        Youtube_Uploader.fileuploadpercentages + ","
                           + Youtube_Uploader.time;

                    default:
                        progress.Report("upload unknown state");
                        return;
                }     
            }
            if (request.QueryString[0] == "nothing")
            {
                progress.Report("Connection Success");
                return;
            }
            if (request.QueryString[0] == "start")
            {
                StartRecrod();
                progress.Report("Recording started");
            }
            if (request.QueryString[0] == "stop")
            {
                dirchanged = false;
                StartRecrod();
                string fileforupload = await WatchDirectory();
                progress.Report("Recording stopped and preparing the file to be shared on youtube");
                await WaitForUnlockedFile(fileforupload);
                uploadedFilesList.Add(fileforupload);
                Youtube_Uploader youtubeupload = new Youtube_Uploader(fileforupload);//uploadedFilesList[0]);
            }
        }
        else
        {
            progress.Report("Nothing have been done");
        }
    }

假设以前的代码看起来像这样:

string result = await SendResponseAsync(request);

SendResultToClient(result);

然后你可以像这样调用这个方法的新版本:

await SendResponseAsync(request, new Progress<string>(s => SendResultToClient(s)));

这将创建一个新的Progress&lt;T&gt; 实例(一个实现IProgress&lt;T&gt; 的内置类),它将调用您的SendResultToClient() 方法将状态消息发送到客户端。

上面的一个变体是允许方法仍然返回一个结果,并且只对那些需要在方法完成之前返回的结果使用progress 参数。在这种情况下,我将return ...some string value...; 更改为progress.Report(...some string value...); 的所有地方都将恢复为原始代码中的方式,您仍将方法声明为async Task&lt;string&gt;,返回result 值,当然,在等待对SendResponseAsync() 的调用完成后,使用该值将结果发送给客户端。 IE。除了添加 IProgress&lt;T&gt; 参数外,调用站点不会改变。


编辑:

解决问题中添加的信息:

  1. 首先,您错误地调用了WebServer 构造函数。您使用的WebServer 代码与几周前的the code I helped someone else 完全相同。在那个问题中,目标是能够将async 方法传递给构造函数。使用Task.Run() 的示例适用于使用WebServer 的一些其他 调用者没有async 方法可以传递的情况。在任务中包装对async 方法的调用是完全错误的,并且不会产生预期的结果。 (实际上,您发布的代码甚至不应该编译,因为 Task.Run() 正在返回 Task&lt;Task&lt;string&gt;&gt; 而委托应该只返回 Task&lt;string&gt;。假设它确实编译,大概有一些 other hack未显示可以解决该编译时错误。)
  2. 要实际使用上面的建议,您需要重构其余代码,以便它可以与建议的设计配合使用。特别是,async 方法需要传递适当的IProgress&lt;T&gt; 实例,该实例用于报告状态。这样做的唯一方法是更改​​最终用于调用该方法的委托的签名,然后当然是传递所需的IProgress&lt;T&gt; 实例。我将在下面提供一个示例来说明这种变化可能是什么样子……

首先,您的WebServer 类需要更改,以接受不同的方法签名:

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

    public WebServer(string[] prefixes, Func<HttpListenerRequest, IProgress<string>, Task> 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, IProgress<string>, Task> 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
                        {
                            await _responderMethod(ctx.Request, new Progress<string>(rstr =>
                            {
                                byte[] buf = Encoding.UTF8.GetBytes(rstr);
                                ctx.Response.ContentLength64 = buf.Length;
                                ctx.Response.OutputStream.Write(buf, 0, buf.Length);
                            });
                            System.Diagnostics.Trace.Write(ctx.Request.QueryString);
                            //ctx.Request.QueryString

                            System.Data.SqlClient.SqlConnectionStringBuilder builder = new System.Data.SqlClient.SqlConnectionStringBuilder();

                        }
                        catch (Exception error)
                        {

                            string ttt = error.ToString();
                        } // 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();
    }
}

那么构造函数调用应该是这样的:

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

当然,您已经修改了SendResponseAsync(),正如我上面解释的那样,它可以接收IProgress&lt;T&gt; 的实例作为参数。

完成所有这些之后,您的 WebServer 类的 Run() 方法中将结果文本写入响应输出流的代码块被封装在一个匿名方法中,该方法用作 Progress&lt;string&gt;Action&lt;string&gt; 委托实例传递给 _responseMethod 委托调用。

注意:

以上内容在您的情况下可能正确,也可能不正确。同样,如果没有一个好的代码示例,就不可能确定。但重要的是要记住,Progress&lt;T&gt; 类在创建时使用当前的SynchronizationContext 以引发其ProgressChanged 事件。在许多情况下,这正是您想要的。但是根据此处调用的上下文,您可能最终会使用线程池来引发事件,这会引入乱序回调的可能性。这当然会导致数据以错误的顺序出现在响应流中。

因此,作为使用Progress&lt;T&gt; 的替代方法,您可能希望使用同步引发事件的自定义类。例如:

class SynchronousProgress<T> : IProgress<T>
{
    public event EventHandler<T> ProgressChanged;

    public SynchronousProgress() { }

    public SynchronousProgress(Action<T> callback)
    {
        ProgressChanged = (sender, e) => callback(e);
    }

    public void Report(T t)
    {
        EventHandler<T> handler = ProgressChanged;

        if (handler != null)
        {
            handler(this, t);
        }
    }
}

要使用它,只需在上面的 Run() 方法示例中将 Progress 替换为 SynchronousProgress


† - 当然忽略迭代器方法。它们具有不同的语义,允许yield return 多次执行。但它们也不能很好地与 async 配合使用,因此在这里不相关。

【讨论】:

  • 彼得谢谢你。你明白了这个想法和问题。现在我添加到我的问题 UPDATE 并在那里我添加了我在 form1 构造函数中为我的 WebServer 制作实例的方式以及为什么我不能像你那样做:await SendResponseAsync(request, new Progress(s => SendResultToClient(s)));我还添加了 WebServer 类。也许你可以看看它,看看如何制作实例。
  • @Sharon:你的代码看起来就像我两周前的code I helped with。但是您的代码示例正在滥用它。如果正在传递的回调实际上已经是async,则在调用WebServer 类构造函数时无需使用Task.Run(),就像您的代码示例中的情况一样。我将编辑我的答案以尝试解决您添加的新问题,但请注意,我确实回答了原始问题,并且一旦提出问题,您应该尽量不要扩展问题。如果您得到一个好的答案,请接受并发布一个包含新详细信息的新问题。
猜你喜欢
  • 2015-04-13
  • 1970-01-01
  • 1970-01-01
  • 2019-05-21
  • 2020-12-04
  • 1970-01-01
  • 2015-12-19
  • 1970-01-01
  • 2012-02-27
相关资源
最近更新 更多