引发事件时,事件处理程序始终为 null

Event handler is always null when raising event

我正在编写一个 class 库,它连接到 Twitter Streaming API 并实时处理连续的 JSON 流。每次从 API 收到一条新推文时,我都想引发一个事件,这样我就可以在调用者 class 中使用 lambda 方法,如下所示:

var stream = new Stream(customerKey,customerSecret,accessToken,accessTokenSecret);
stream.Start();

// Handler
stream.TweetReceivedEvent += (sender, tweetargs) =>
{
   Console.WriteLine(tweetargs.Tweet.ToString());
};

但是,我不确定该怎么做。目前,我创建了一个名为 Stream 的 class,其中包含与连接到 Twitter Streaming API 相关的逻辑(仅包含此 [= 中的 Start() 方法36=] 下面为简洁起见):

public async Task Start()
{
    //Twitter Streaming API
    string stream_url = "https://stream.twitter.com/1.1/statuses/filter.json";

    string trackKeywords = "twitter";
    string followUserId = "";
    string locationCoord = "";

    string postparameters = (trackKeywords.Length == 0 ? string.Empty : "&track=" + trackKeywords) +
                            (followUserId.Length == 0 ? string.Empty : "&follow=" + followUserId) +
                            (locationCoord.Length == 0 ? string.Empty : "&locations=" + locationCoord);

    if (!string.IsNullOrEmpty(postparameters))
    {
        if (postparameters.IndexOf('&') == 0)
            postparameters = postparameters.Remove(0, 1).Replace("#", "%23");
    }

    //Connect
    webRequest = (HttpWebRequest) WebRequest.Create(stream_url);
    webRequest.Timeout = -1;
    webRequest.Headers.Add("Authorization", GetAuthHeader(stream_url + "?" + postparameters));

    Encoding encode = Encoding.GetEncoding("utf-8");
    if (postparameters.Length > 0)
    {
        webRequest.Method = "POST";
        webRequest.ContentType = "application/x-www-form-urlencoded";

        byte[] _twitterTrack = encode.GetBytes(postparameters);

        webRequest.ContentLength = _twitterTrack.Length;
        var _twitterPost = webRequest.GetRequestStream();
        _twitterPost.Write(_twitterTrack, 0, _twitterTrack.Length);
        _twitterPost.Close();
    }

    webRequest.BeginGetResponse(ar =>
    {
        var req = (WebRequest)ar.AsyncState;

        using (var response = req.EndGetResponse(ar))
        {
            using (var reader = new StreamReader(response.GetResponseStream()))
            {
                while (!reader.EndOfStream)
                {
                    // Deserialize the JSON obj to type Tweet
                    var jsonObj = JsonConvert.DeserializeObject<Tweet>(reader.ReadLine(), new JsonSerializerSettings());

                    Console.WriteLine(jsonObj.Text);
                    Raise(TweetReceivedEvent, new TweetReceivedEventArgs(jsonObj));
                }
            }
        }

    }, webRequest);
}

在同一个 class 中,我创建了一个事件和一个委托,如下所示:

public event TweetReceivedHandler TweetReceivedEvent;
public delegate void TweetReceivedHandler(TwitterStreamClient s, TweetEventArgs e);

并且 Raise 方法调用我的测试器中的 EventHandler 方法 class:

public void Raise(TweetReceivedHandler handler, TweetEventArgs e)
        {
            if (handler != null)
            {
                handler(this, e);
            }
        }

然而,当我调试并单步执行 Raise 方法时,处理程序始终为空。我在这里错过了什么?如您所见,我已经采取了一些步骤来使此方法异步并返回一个任务,尽管我不确定这是正确的操作过程。

如果您需要任何说明,请随时提出,如果您能解释我需要做什么,我将永远感激您!如果我完全理解错误的一端,请提前道歉!

如果您想查看完整代码,class 有一个(稍旧的)版本 https://github.com/adaam2/APoorMansTwitterStreamingClient/blob/master/TwitterClient/Infrastructure/Utility/TwitterStreamClient.cs

正如 Hans 所建议的,查看引发的事件的关键是确保在可以引发事件之前订阅它。否则,它可能会在您有机会订阅它之前被筹集到。例如:

var stream = new Stream(customerKey,customerSecret,accessToken,accessTokenSecret);

// Handler
stream.TweetReceivedEvent += (sender, tweetargs) =>
{
   Console.WriteLine(tweetargs.Tweet.ToString());
};

stream.Start();

就修复 Start() 方法而言,编写 async 方法的关键是确保在其中使用 await。理想情况下,方法中启动的 所有 异步操作都是可等待的,这样您就可以在整个过程中使用简化的 await 语法。

在你的例子中,我会推荐更像这样的东西:

public async Task Start()
{
    //Twitter Streaming API
    string stream_url = "https://stream.twitter.com/1.1/statuses/filter.json";

    string trackKeywords = "twitter";
    string followUserId = "";
    string locationCoord = "";

    string postparameters = (trackKeywords.Length == 0 ? string.Empty : "&track=" + trackKeywords) +
                            (followUserId.Length == 0 ? string.Empty : "&follow=" + followUserId) +
                            (locationCoord.Length == 0 ? string.Empty : "&locations=" + locationCoord);

    if (!string.IsNullOrEmpty(postparameters))
    {
        if (postparameters.IndexOf('&') == 0)
            postparameters = postparameters.Remove(0, 1).Replace("#", "%23");
    }

    //Connect
    webRequest = (HttpWebRequest) WebRequest.Create(stream_url);
    webRequest.Timeout = -1;
    webRequest.Headers.Add("Authorization", GetAuthHeader(stream_url + "?" + postparameters));

    Encoding encode = Encoding.GetEncoding("utf-8");

    if (postparameters.Length > 0)
    {
        webRequest.Method = "POST";
        webRequest.ContentType = "application/x-www-form-urlencoded";

        byte[] _twitterTrack = encode.GetBytes(postparameters);

        webRequest.ContentLength = _twitterTrack.Length;
        var _twitterPost = await webRequest.GetRequestStreamAsync();
        await _twitterPost.WriteAsync(_twitterTrack, 0, _twitterTrack.Length);
        _twitterPost.Close();
    }

    using (var response = await webRequest.GetResponseAsync())
    {
        using (var reader = new StreamReader(response.GetResponseStream()))
        {
            while (!reader.EndOfStream)
            {
                // Deserialize the JSON obj to type Tweet
                var jsonObj = JsonConvert.DeserializeObject<Tweet>(await reader.ReadLineAsync(), new JsonSerializerSettings());

                Console.WriteLine(jsonObj.Text);
                Raise(TweetReceivedEvent, new TweetReceivedEventArgs(jsonObj));
            }
        }
    }
}

注意,我在上面四个地方使用了...Async()方法:获取请求流、写入请求流、获取响应、处理响应流。通过这种方式,可以直接、逐步地编写方法的逻辑,同时仍然允许异步操作。也就是说,该方法将 return 并且在这些异步操作正在进行时可以继续执行当前线程,并且该方法将在它们完成后恢复执行。

最重要的是,这样做可以确保 Task 由该方法编辑的对象 return 在整个操作完成之前不会自行完成。这样,您实际上可以 完全删除 事件并依靠 Task 对象本身来发出完成信号。然后你的呼叫站点看起来像这样:

var stream = new Stream(customerKey,customerSecret,accessToken,accessTokenSecret);

await stream.Start();
Console.WriteLine(tweetargs.Tweet.ToString());


最后,还有一个建议:我强烈建议您使用 Stream 以外的名称作为您的 class。在尝试阅读和维护代码时,名称 Stream 实际上肯定会造成混淆。