在循环中使用yield关键字来更新C#中的进度条

发布于 2024-11-07 09:22:28 字数 164 浏览 4 评论 0原文

我可以使用yield关键字更新循环的进度吗?

foreach(something obj in somelist)
{
    yield return obj;
}

我知道我可以做到这一点,但如何使用这些值来更新进度条?

谢谢。

Can I update a progress of a loop using the yield keyword?

foreach(something obj in somelist)
{
    yield return obj;
}

I know I can do this, but how do I use the values to update a progress bar ?

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

紅太極 2024-11-14 09:22:28

根据您的评论,您是

正在尝试更新进度
将 wcf 服务中的数据传输到
窗口窗体

这是我在最近的项目中使用的解决方案。示例代码适用于下载文件(即 pdf、jpg 等),但也可以应用于任何可流式传输的数据。

第一个重要注意事项是,您的 WCF 绑定选择将决定这是否可行。如果您使用 basicHttpBinding 或 netTcpBinding,那么该方法应该可以工作,但是如果您使用 WsHttpBinding,那么您就不走运了。

示例绑定可能类似于:

  <basicHttpBinding>
    <binding name="ResourceServiceBasicHttpBinding" maxReceivedMessageSize="20971520" sendTimeout="00:05:00" receiveTimeout="00:05:00" transferMode="Streamed" messageEncoding="Mtom">
      <security mode="None" />
    </binding>
  </basicHttpBinding>

需要注意的重要一点是传输模式“Streamed”。

要报告 WCF 服务和 WinForm 之间的数据传输进度,您需要确保 WCF 服务使用的是 MessageContracts 而不是 DataContracts。原因是 MessageContract 允许您将单个流作为响应正文。例如,您可能有一个请求/响应消息契约,如下所示:

请求...

[MessageContract]
public class DownloadFileRequest
{
  [MessageHeader(MustUnderstand = true)]
  public Guid FileId { get; set; }
}

响应...

[MessageContract]
public class DownloadFileResponse : IDisposable
{
  [MessageHeader(MustUnderstand = true)]
  public Int32 FileSize { get; set; }

  [MessageHeader(MustUnderstand = true)]
  public String FileName { get; set; }

  [MessageBodyMember(Order = 1)]
  public Stream FileByteStream { get; set; }

  public void Dispose()
  {
    if (FileByteStream != null)
      FileByteStream.Dispose();
  }
}

现在一切都很好,但是您需要有一种方法来报告下载(或上传)进度给客户。不幸的是,.NET 没有提供一种开箱即用的好方法来执行此操作...正如其他人提到的,您正在寻找事件...具体来说,是“ObservableStream”的实现。再说一遍,这是我喜欢使用的实现...

向订阅者报告读取或写入操作的事件参数...

public class ObservableStreamEventArgs : EventArgs
{
  private readonly Int64 _length;
  private readonly Int64 _position;
  private readonly Int32 _numberOfBytes;

  public Int64 StreamLength { get { return _length; } }
  public Int64 StreamPosition { get { return _position; } }
  public Int32 NumberOfBytes { get { return _numberOfBytes; } }

  public ObservableStreamEventArgs(Int32 numberOfBytes, Int64 position, Int64 length)
  {
    _numberOfBytes = numberOfBytes;
    _position = position;
    _length = length;
  }
}

当然还有流实现本身...

public class ObservableStream : Stream
{
  private readonly Stream _baseStream;
  private Int64 _streamLength;

  public event EventHandler<ObservableStreamEventArgs> BytesRead;
  public event EventHandler<ObservableStreamEventArgs> BytesWritten;

  public ObservableStream(Stream stream)
  {
    Verify.NotNull(stream);

    _baseStream = stream;
    _streamLength = _baseStream.Length;
  }

  public ObservableStream(Stream stream, Int64 length)
  {
    Verify.NotNull(stream);

    _baseStream = stream;
    _streamLength = length;
  }

  public override bool CanRead      
  {        
    get { return _baseStream.CanRead; }
  }

  public override bool CanSeek
  {
    get { return _baseStream.CanSeek; }
  }

  public override bool CanWrite
  {
    get { return _baseStream.CanWrite; }
  }

  public override void Flush()
  {
    _baseStream.Flush();
  }

  public override Int64 Length
  {
      get { return _streamLength; }
    }

    public override Int64 Position
    {
      get { return _baseStream.Position; }
      set { _baseStream.Position = value; }
    }

    public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count)
    {
      Int32 bytesRead = _baseStream.Read(buffer, offset, count);

      var listeners = BytesRead;
      if (listeners != null)
        listeners.Invoke(this, new ObservableStreamEventArgs(bytesRead, Position, Length));

      return bytesRead;
    }

    public override Int64 Seek(Int64 offset, SeekOrigin origin)
    {
      return _baseStream.Seek(offset, origin);
    }

    public override void SetLength(Int64 value)
    {
      _streamLength = value;
    }

    public override void Write(Byte[] buffer, Int32 offset, Int32 count)
    {
      _baseStream.Write(buffer, offset, count);

      var listeners = BytesWritten;
      if (listeners != null)
        listeners.Invoke(this, new ObservableStreamEventArgs(count, Position, Length));
    }

    public override void Close()
    {
      _baseStream.Close();

      base.Close();
    }

    protected override void Dispose(bool disposing)
    {
      if (disposing)
        _baseStream.Dispose();

      base.Dispose(disposing);
    }
  }

最后,您必须使用以下数据 :你的 Windows 客户端...

像平常一样简单地调用你的服务方法(ServiceChannelFactory 是 ChannelFactory 实现的包装器...不用担心)。

 DownloadFileResponse response;
 using (var channel = _serviceChannelFactory.CreateReportServiceChannel())
   response channel.Service.GenerateReport(request)

然后连接到您的可观察流以更新用户界面(显然必须在后台线程上工作,否则您将阻塞用户界面)

 using (var downloadStream = response.FileByteStream)
 using (var observableStream = new ObservableStream(downloadStream, response.FileSize))
 using (var fileStream = new FileStream(tempFile, FileMode.OpenOrCreate, FileAccess.Write))
 {
   observableStream.BytesRead += (sender, e) => _view.UpdateProgress(Convert.ToInt32(e.StreamPosition * 100 / e.StreamLength));

   do
   {
     bytesRead = observableStream.Read(buffer, 0, 4096);
     fileStream.Write(buffer, 0, bytesRead);
   } while (bytesRead > 0);
 }

编辑

哦,服务方法本身非常简单...忘记了将其包括在内;您所要做的就是返回您的流:

  return new DownloadFileResponse
           {
             FileName = report.FileName,
             FileSize = report.Data.Length,
             FileByteStream = new MemoryStream(report.Data, false)
           };

Based on your comment that you are

trying to update the progress of
transfer of data in a wcf service to a
windows form

Here is the solution that I have used on a recent project. The sample code is geared towards downloading a file (i.e., pdf, jpg, etc), but can be applied to any streamable data.

The first important note, your WCF binding choice will determine whether or not this is even possible. If you are using basicHttpBinding or netTcpBinding, then the approach should work, however if you are using WsHttpBinding, you are out of luck.

A sample binding may look something like:

  <basicHttpBinding>
    <binding name="ResourceServiceBasicHttpBinding" maxReceivedMessageSize="20971520" sendTimeout="00:05:00" receiveTimeout="00:05:00" transferMode="Streamed" messageEncoding="Mtom">
      <security mode="None" />
    </binding>
  </basicHttpBinding>

The important thing to note is the transfer mode "Streamed".

To report progress on the transfer of data between a WCF service and a WinForm you need to ensure that your WCF service is using MessageContracts over DataContracts. The reason is that a MessageContract allows you to have a single stream as the response body. For instance, you may have a request/response message contract as follows:

The request...

[MessageContract]
public class DownloadFileRequest
{
  [MessageHeader(MustUnderstand = true)]
  public Guid FileId { get; set; }
}

The response...

[MessageContract]
public class DownloadFileResponse : IDisposable
{
  [MessageHeader(MustUnderstand = true)]
  public Int32 FileSize { get; set; }

  [MessageHeader(MustUnderstand = true)]
  public String FileName { get; set; }

  [MessageBodyMember(Order = 1)]
  public Stream FileByteStream { get; set; }

  public void Dispose()
  {
    if (FileByteStream != null)
      FileByteStream.Dispose();
  }
}

Now this is all well and good, but you need to have a way to report that download (or upload) progress back to the client. Unfortunately, .NET doesn't provide a nice way out of the box to do this... as mentioned by others, you are looking for events... specifically, the implementation of an "ObservableStream". Again, here is the implementation that I like to use....

The event args to report a read or write operation to a subscriber...

public class ObservableStreamEventArgs : EventArgs
{
  private readonly Int64 _length;
  private readonly Int64 _position;
  private readonly Int32 _numberOfBytes;

  public Int64 StreamLength { get { return _length; } }
  public Int64 StreamPosition { get { return _position; } }
  public Int32 NumberOfBytes { get { return _numberOfBytes; } }

  public ObservableStreamEventArgs(Int32 numberOfBytes, Int64 position, Int64 length)
  {
    _numberOfBytes = numberOfBytes;
    _position = position;
    _length = length;
  }
}

And of course the stream implementation itself...

public class ObservableStream : Stream
{
  private readonly Stream _baseStream;
  private Int64 _streamLength;

  public event EventHandler<ObservableStreamEventArgs> BytesRead;
  public event EventHandler<ObservableStreamEventArgs> BytesWritten;

  public ObservableStream(Stream stream)
  {
    Verify.NotNull(stream);

    _baseStream = stream;
    _streamLength = _baseStream.Length;
  }

  public ObservableStream(Stream stream, Int64 length)
  {
    Verify.NotNull(stream);

    _baseStream = stream;
    _streamLength = length;
  }

  public override bool CanRead      
  {        
    get { return _baseStream.CanRead; }
  }

  public override bool CanSeek
  {
    get { return _baseStream.CanSeek; }
  }

  public override bool CanWrite
  {
    get { return _baseStream.CanWrite; }
  }

  public override void Flush()
  {
    _baseStream.Flush();
  }

  public override Int64 Length
  {
      get { return _streamLength; }
    }

    public override Int64 Position
    {
      get { return _baseStream.Position; }
      set { _baseStream.Position = value; }
    }

    public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count)
    {
      Int32 bytesRead = _baseStream.Read(buffer, offset, count);

      var listeners = BytesRead;
      if (listeners != null)
        listeners.Invoke(this, new ObservableStreamEventArgs(bytesRead, Position, Length));

      return bytesRead;
    }

    public override Int64 Seek(Int64 offset, SeekOrigin origin)
    {
      return _baseStream.Seek(offset, origin);
    }

    public override void SetLength(Int64 value)
    {
      _streamLength = value;
    }

    public override void Write(Byte[] buffer, Int32 offset, Int32 count)
    {
      _baseStream.Write(buffer, offset, count);

      var listeners = BytesWritten;
      if (listeners != null)
        listeners.Invoke(this, new ObservableStreamEventArgs(count, Position, Length));
    }

    public override void Close()
    {
      _baseStream.Close();

      base.Close();
    }

    protected override void Dispose(bool disposing)
    {
      if (disposing)
        _baseStream.Dispose();

      base.Dispose(disposing);
    }
  }

Finally, you have to consume the data in your windows client...

Simple call your service method as you normally would (ServiceChannelFactory is a wrapper around a ChannelFactory implementation... don't worry about that).

 DownloadFileResponse response;
 using (var channel = _serviceChannelFactory.CreateReportServiceChannel())
   response channel.Service.GenerateReport(request)

and then hookup to your observable stream to update the ui (obviously must be working on a background thread, otherwise you will just block the ui)

 using (var downloadStream = response.FileByteStream)
 using (var observableStream = new ObservableStream(downloadStream, response.FileSize))
 using (var fileStream = new FileStream(tempFile, FileMode.OpenOrCreate, FileAccess.Write))
 {
   observableStream.BytesRead += (sender, e) => _view.UpdateProgress(Convert.ToInt32(e.StreamPosition * 100 / e.StreamLength));

   do
   {
     bytesRead = observableStream.Read(buffer, 0, 4096);
     fileStream.Write(buffer, 0, bytesRead);
   } while (bytesRead > 0);
 }

EDIT

Oh, the service method itself is pretty simple... forgot to include it; all you have to do is return your stream:

  return new DownloadFileResponse
           {
             FileName = report.FileName,
             FileSize = report.Data.Length,
             FileByteStream = new MemoryStream(report.Data, false)
           };
长发绾君心 2024-11-14 09:22:28

正如用户 digEmAll 所评论的,您更有可能想为此使用事件。

但是,如果您想要一个更新yield return进度的解决方案,您可能希望在迭代集合之前获取元素的数量。然后,对于返回的每个元素,您可以将进度条更新一。

如果您详细阐述一下您的问题,也许会很有用 - 您到底想实现什么目标。

As commented by user digEmAll, it is more likely that you want to use an event for this.

But if you want a solution that updates progress on yield return, you will probably want to get the number of elements before iterating through your collection. Then, for each element you get returned, you can update your progressbar by one.

Perhaps it would be useful if you elaborated your question a bit - what exactly are you trying to accomplish.

静若繁花 2024-11-14 09:22:28

为什么不从循环内部更新进度条?

Why not update the progress bar from inside the loop?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文