如何使用Timer访问来自另一个类的成员?

本文关键字:成员 另一个 何使用 Timer 访问 | 更新日期: 2023-09-27 17:49:46

我目前正在编写一个类来计算一段时间内的平均下载速度,取定义数量的样本。我认为这将工作的方式是,这个类运行一个计时器对象,它调用一个方法在说的类,将查看下载的字节(维护在父类,FTPDownloadFile),然后将该样本存储在队列中。然而,我的问题是访问下载的字节数。

我访问该信息的方法是通过在下载计算类构造时传递的引用,然而,似乎我没有正确理解/使用引用。传入的变量总是显示为0,即使我可以看到原始变量的变化。

谁能告诉我我做错了什么/建议一个更好的方法让我完成我想做的事?

首先,这里是处理下载速度计算的类:

public class SpeedCalculator
    {
        private const int samples = 5;
        private const int sampleRate = 1000; //In milliseconds
        private int bytesDownloadedSinceLastQuery;
        private System.Threading.Timer queryTimer;
        private Queue<int> byteDeltas = new Queue<int>(samples);
        private int _bytesDownloaded;
        public SpeedCalculator(ref int bytesDownloaded)
        {
            _bytesDownloaded = bytesDownloaded;
        }
        public void StartPolling()
        {
            queryTimer = new System.Threading.Timer(this.QueryByteDelta, null, 0, sampleRate);
        }
        private void QueryByteDelta(object data)
        {
            if (byteDeltas.Count == samples)
            {
                byteDeltas.Dequeue();
            }
            byteDeltas.Enqueue(_bytesDownloaded - bytesDownloadedSinceLastQuery);
            bytesDownloadedSinceLastQuery = _bytesDownloaded;
        }
        /// <summary>
        /// Calculates the average download speed over a predefined sample size.
        /// </summary>
        /// <returns>The average speed in bytes per second.</returns>
        public float GetDownloadSpeed()
        {
            float speed;
            try
            {
                speed = (float)byteDeltas.Average() / ((float)sampleRate / 1000f);
            }
            catch {speed = 0f;}
            return speed;
        }

这个类包含在我的FTPDownloadFile类中:

class FTPDownloadFile : IDisposable
{
    private const int recvBufferSize = 2048;
    public int bytesDownloaded;
    public SpeedCalculator Speed;
    private FileStream localFileStream;
    FtpWebResponse ftpResponse;
    Stream ftpStream;
    FtpWebRequest ftpRequest;
    public List<string> log = new List<string>();
    private FileInfo destFile;
    public event EventHandler ConnectionEstablished;
    public FTPDownloadFile()
    {
        bytesDownloaded = 0;
        Speed = new SpeedCalculator(ref bytesDownloaded);
    }
    public void GetFile(string host, string remoteFile, string user, string pass, string localFile)
    {
        //Some code to start the download...
        Speed.StartPolling();
    }
    public class SpeedCalculator {...}
}

如何使用Timer访问来自另一个类的成员?

在理解c#中的ref参数时,这是一个常见的问题。您可以看到,与c++不同,c#中没有的实值引用

在c++中,当按引用传递时,实际上是在内部传递指向变量的指针。因此,可以有一个类型为"int&"的类成员变量,它实际上是对存储在其他地方的整数的引用。

在c#中,'ref'或'out'参数的工作方式类似,但没有人谈论指针。不能存储引用。你不能有一个"ref"类成员。看看你的类:sotrage变量的类型是'int',普通的'int',而不是引用。

实际上是通过ref传递该值,但随后将其复制到成员变量。"引用"在构造函数结束的地方消失了。

要实现它,你必须保留实际的源对象,并且要么引入一个强依赖,要么通过接口引入一个弱依赖,或者用惰性/函数的方式——通过委托

示例#1:strong reference
public class SpeedCalculator
{
    private const int samples = 5;
    private const int sampleRate = 1000; //In milliseconds
    private int bytesDownloadedSinceLastQuery;
    private System.Threading.Timer queryTimer;
    private Queue<int> byteDeltas = new Queue<int>(samples);
    private FTPDownloadFile downloader; // CHANGE
    public SpeedCalculator(FTPDownloadFile fileDownloader) // CHANGE
    {
        downloader = fileDownloader;
    }
    public void StartPolling()
    {
        queryTimer = new System.Threading.Timer(this.QueryByteDelta, null, 0, sampleRate);
    }
    private void QueryByteDelta(object data)
    {
        if (byteDeltas.Count == samples)
        {
            byteDeltas.Dequeue();
        }
        byteDeltas.Enqueue(_bytesDownloaded - bytesDownloadedSinceLastQuery);
        bytesDownloadedSinceLastQuery = downloader.bytesDownloaded; // CHANGE
    }
//and in the other file
public FTPDownloadFile()
{
    bytesDownloaded = 0;
    Speed = new SpeedCalculator( this ); // CHANGE
}

在c#中,每个对象(class MyObject)都是通过引用或隐式指针传递的,因此通过参数获取FTPDownloadFile并将其分配给成员变量不会复制它,它实际上是通过ref传递的(另一方面,值(int, decimal, ..)和结构体(struct MyThing)总是通过值传递,因此您的原始_bytes = bytes制作了int的副本)。因此,稍后,我可以只查询

示例#2:"weak" reference

public interface IByteCountSource
{
    int BytesDownloaded {get;}
}
public class FTPDownloadFile : IDisposable, IByteCountSource
{
    .....
    public int BytesDownloaded { get { return bytesDownloaded; } }
    .....
    public FTPDownloadFile()
    {
       bytesDownloaded = 0;
       Speed = new SpeedCalculator( this ); // note no change versus Ex#1 !
    }
}
public class SpeedCalculator
{
    ....
    private IByteCountSource bts;
    public SpeedCalculator(IByteCountSource countSource) // no "FTP" information!
    {
        this.bts = countSource;
    }
    ...
    private void QueryByteDelta(object data)
    {
        ....
        bytesDownloadedSinceLastQuery = bts.BytesDownloaded;
    }

第一个例子是快速和肮脏的。一般来说,我们通常希望类尽可能少地了解所有其他类。那么为什么SpeedCalculator要知道FTPDownloadFile呢?它只需要知道当前的字节数。所以我引入了一个界面来"隐藏"实际的源代码。现在,SpeedCalculator可以从任何实现该接口的对象中获取值——无论是FTPDownloadFile、HTTPDownloadFile还是DummyTestDownloader

示例#3:委托、匿名函数等

public class SpeedCalculator
{
    ....
    private Func<int> bts;
    public SpeedCalculator(Func<int> countSource)
    {
        this.bts = countSource;
    }
    ...
    private void QueryByteDelta(object data)
    {
        ....
        bytesDownloadedSinceLastQuery = bts();
    }
// and in the other file
private int getbytes() { return bytesDownloaded; }
public FTPDownloadFile()
{
    bytesDownloaded = 0;
    Speed = new SpeedCalculator( this.getbytes ); // note it is NOT a getbytes() !
}
// or even
public FTPDownloadFile()
{
    bytesDownloaded = 0;
    Speed = new SpeedCalculator( () => this.bytesDownloaded ); // CHANGE
}

有界面的例子很漂亮,但是界面"小"。一个是可以的,但有时您需要引入几十个这样的一个属性或一个方法接口,这会变得有些无聊和混乱。特别是如果所有这些都是"内部实现",无论如何都不会发布给任何其他人使用。您可以很容易地使用一个简短的lambda删除这样小的接口,如第三个示例所示。我没有接收和存储实现接口的对象,而是将参数更改为Func。这种方式我需要得到"一个返回INT的方法"。它们,我传递给一些方法。请注意,在new SpeedCalculator期间,我没有调用this.getbytes(),我传递了没有括号的方法-这导致该方法被包装成Func委托,稍后将作为bts()调用,并将返回当前计数器。这个getbytes很少使用,只在这个地方使用——所以我甚至可以完全删除它,并在构造函数调用的地方编写匿名函数,就像你在"or even"部分看到的那样。

然而,我建议你现在坚持使用接口,它们更容易阅读和理解。