我怎样才能使该方法首先返回一个字符串然后继续其余的代码?

时间:2015-11-14 17:59:09

标签: c# .net winforms

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

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构造函数中使用方法SendResponseAsync为WebServer创建实例的方法。

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

1 个答案:

答案 0 :(得分:1)

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

基本问题是SendResponseAsync()方法可以像任何其他方法async一样,只返回单个值。你不能让任何方法多次返回,这对任何其他类型的async方法都是正确的。

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

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

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<T>(一个实现IProgress<T>的内置类)的新实例,它将调用您的SendResultToClient()方法将状态消息发送给客户端。

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


修改

要解决问题中添加的信息:

  1. 首先,您正在错误地调用WebServer构造函数。几周之前,您使用的WebServer代码与the code I helped someone else完全相同。在那个问题中,目标是能够将async方法传递给构造函数。使用Task.Run()的示例用于使用WebServer但没有async的某些其他来电者的情况通过的方法。在任务中包含对async方法的调用是完全错误的,不会产生所需的结果。 (实际上,你发布的代码甚至不应该编译,因为Task.Run()正在返回Task<Task<string>>,而委托应该只返回Task<string>。假设它确实编译,假设有一些其他 hack未显示可解决该编译时错误。)
  2. 要实际使用上面的建议,您需要重构其余代码,以便它可以使用建议的设计。特别是,async方法需要传递用于报告状态的相应IProgress<T>实例。唯一的方法是更改​​最终用于调用该方法的委托的签名,然后稍后传递所需的IProgress<T>实例。我将提供一个示例,说明下面的变化可能是什么......
  3. 首先,您的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<T>的实例作为参数。

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

    注意:

    在您的方案中,上述内容可能正确也可能不正确。同样,如果没有一个好的代码示例,就不可能确切地知道。但请务必记住,Progress<T>类在创建时使用当前SynchronizationContext来提升其ProgressChanged事件。在许多情况下,这正是您想要的。但是根据这里调用的上下文,你最终可能会使用线程池来引发事件,从而引入了无序回调的可能性。这当然会导致数据以错误的顺序出现在响应流中。

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

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

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


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