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

时间:2015-04-18 16:14:28

标签: c# asynchronous httprequest twitter-streaming-api

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

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

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

但是,我不确定如何做到这一点。目前,我已经创建了一个名为Stream的类,其中保存了与连接到Twitter Streaming API相关联的逻辑(为了简洁起见,下面仅使用此类中的Start()方法):

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

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

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

Raise方法调用我的测试人员类中的EventHandler方法:

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

但是,当我调试并逐步执行Raise方法时,Handler始终为null。我在这里错过了什么?正如您所看到的,我已采取一些步骤使此方法异步并返回任务,但我不确定这是正确的操作过程。

如果您需要任何澄清,请随时提出要求,如果您能解释我需要做什么,我将永远感激您!如果我完全弄错了棍子的末端,请提前道歉!

如果您想查看完整代码,可以在https://github.com/adaam2/APoorMansTwitterStreamingClient/blob/master/TwitterClient/Infrastructure/Utility/TwitterStreamClient.cs存在此类(稍微更旧)的版本。

1 个答案:

答案 0 :(得分:1)

正如Hans建议的那样,看到引发事件的关键是确保你在之前订阅它可能会引发事件。否则,在您有机会订阅之前,它可能会被提升。 E.g:

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()方法:获取请求流,写入请求流,获取响应以及处理响应流。通过这种方式,该方法的逻辑可以以直接的,逐步的方式编写,但仍然允许异步操作。也就是说,当这些异步操作正在进行时,该方法将返回并执行当前线程,并且该方法将在完成后继续执行。

最重要的是,这样做可以确保方法返回的Task对象本身不会完成,直到整个操作完成。通过这种方式,您实际上可以完全删除事件,并依赖Task对象本身来表示完成。那么你的通话网站就是这样的:

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

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


最后,还有一个建议:我强烈建议您为班级使用Stream以外的名称。实际上保证名称Stream在尝试读取和维护代码时会引起混淆。