通过多线程截取VCD片断的有趣问题!!(50分)

  • 主题发起人 主题发起人 北京男人
  • 开始时间 开始时间

北京男人

Unregistered / Unconfirmed
GUEST, unregistred user!
我想通过一个级别底的线程TRead读取VCD内容(用BlockRead函数读取),然后为了怕
TRead线程死掉,另外创建了个级别很高底TWatch监视线程监视TRead,如果TRead读了5秒还没
反应则文件指针自动跳过1k继续读取。。。
结果发现TRead线程还是死掉了,发现TRead线程一旦读了光盘比较难读的片断TWatch线程
就几乎失去监控作用(TWatch.execute方法根本不执行了!!)难道读取硬件的的级别比
任何多线程级别都高吗?
 
关注......
 
其实我觉得不用如此.不过俺没有试过,不知道是不是一样
大家都知道,在windows里可以把com口作为一个设备来读取,
简单的OpenFile,ReadFile,WriteFile就可以进行读写了,而判断有没有超时可以
引进COMMTIMEOUTS,而COMMTIMEOUTS种的ReadIntervalTimeout 正式为了判断读取的
任意两字符间的间隙而来的,是不是也可以用这种方式来进行vcd文件的读取呢
如果超时了就是。。。。
别打我头,我只是这么认为,没看文档,没有试
 
lvxq说的有道理
 
我用楼上说的方法改写了,完整代码如下:
以下是我改写后的代码,能跳过死读并且FileRead成功,但超时等待事件特别长!
几乎那个函数不起作用!
在ReadFile那里还是等很长时间!

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ComCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
pb: TProgressBar;
FLabelStatus: TLabel;
lbl_Total: TLabel;
edt_TimeOut: TEdit;
UpDown1: TUpDown;
Label1: TLabel;
od1: TOpenDialog;
sd1: TSaveDialog;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;


var
Form1: TForm1;
bBreak: Boolean;
function ByteToM(ByteCount: integer): Real;
//得到文件大小,单位为字节,失败则返回$FFFFFFFF
function MyGetFileSize(HFile: THANDLE): DWORD;
implementation

uses Math;

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
Label
TimeOutRead;
var
fIn, fOut:THANDLE;
Buf: array [0..2047] of char;
//nRead : PDWORD;
nRead, nTotalRead,nWrite: LongWord;
TimeOut: COMMTIMEOUTS;
begin

if od1.Execute then

begin

fIn:= CreateFile(PChar(od1.FileName), GENERIC_READ ,
FILE_SHARE_READ , nil, //security
OPEN_EXISTING ,
FILE_ATTRIBUTE_ARCHIVE or
FILE_ATTRIBUTE_COMPRESSED or
FILE_ATTRIBUTE_HIDDEN or
FILE_ATTRIBUTE_NORMAL or
FILE_ATTRIBUTE_OFFLINE or
FILE_ATTRIBUTE_READONLY or
FILE_ATTRIBUTE_SYSTEM or
FILE_ATTRIBUTE_TEMPORARY,
0);
//ShowMessage('fIn=' + IntToStr(fIn));
if fIn = INVALID_HANDLE_VALUE then

begin

MessageDlg('打开文件失败!',mtError, [mbOK], 0);
exit;
end;

lbl_Total.Caption:= 'VCD文件总长:' + FloatToStr(ByteToM(MyGetFileSize(fIn))) + ' M';
nTotalRead:= 0;
pb.Min:= 0;
pb.Max:= Round(ByteToM(MyGetFileSize(fIn)));
bBreak:= False;
//Out file
sd1.DefaultExt:= od1.DefaultExt;
sd1.Filter:= od1.Filter;
if sd1.Execute then

begin

fOut:= CreateFile(PChar(sd1.FileName), GENERIC_WRITE ,
0 , nil, //security
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL , 0);

//ShowMessage('fOut=' + IntToStr(fOut));
if fOut = INVALID_HANDLE_VALUE then

begin

MessageDlg('创建文件失败!',mtError, [mbOK], 0);
exit;
end;

//Set time out
TimeOut.ReadIntervalTimeout:= StrToInt(edt_TimeOut.Text);
SetCommTimeouts(fIn, TimeOut);
//begin
read vcd.
try
try
//Repeat
{if }
TimeOutRead:
while ReadFile(fIn, Buf, SizeOf(Buf), LongWord(nRead), nil) {and (nRead <> 0)}do

begin

if bBreak then
break;
WriteFile(fOut, Buf, SizeOf(Buf), LongWord(nWrite), nil);
//end;


nTotalRead:= nTotalRead + nRead;
FLabelStatus.Caption:= '已经截取了:【'
+ IntToStr(nTotalRead) + '】个字节('
+ FloatToStr(nTotalRead / Power(2,20)) + 'MB)';
pb.Position:= Round(ByteToM(nTotalRead));
Application.ProcessMessages;
if bBreak then
break;
end;

//如果碰到VCD损坏块并且读取超时则跳出
i if not GetCommTimeOuts(fIn, TimeOut) and (nRead = 0) then

begin

SetFilePointer (fIn, SizeOf(Buf), nil, FILE_CURRENT);
goto TimeOutRead;
end;


//until
// (nRead = 0) or (nRead <> nWrite);
finally
CloseHandle(fIn);
CloseHandle(fOut);
//ReadFile(
end;

MessageDlg('VCD 截取成功!',mtInformation, [mbOK], 0);
except
MessageDlg('VCD 截取失败!',mtError, [mbOK], 0);
end;

end;

//save dialog
end;

//Open dialog
end;


function ByteToM(ByteCount: integer): Real;
begin

Result:= ByteCount / Power(2, 20);
end;


function MyGetFileSize(HFile: THANDLE): DWORD;
var
dwSizeLow, dwSizeHigh, dwError: DWORD;
begin

Result:= $FFFFFFFF;
dwSizeLow := GetFileSize (hFile, @dwSizeHigh) ;
if (dwSizeLow = $FFFFFFFF) then

begin

dwError := GetLastError();
if dwError <> NO_ERROR then

begin

// Deal with that failure.
MessageDlg('读取文件大小时发生错误,错误代号为:' + IntToStr(dwError)
,mtError, [mbOK], 0);
exit;
end;

end;

// End of error handler.
Result:= dwSizeLow or (dwSizeHigh shl 32);

end;



procedure TForm1.Button2Click(Sender: TObject);
begin

bBreak:= true;
end;


end.
 
DWORD ReadIntervalTimeout;

DWORD ReadTotalTimeoutMultiplier;

DWORD ReadTotalTimeoutConstant;
看来这位兄弟还没彻底理解超时的概念,那我再解释一下。对于读操作而言,上面的上个
参数是相关的,第一个,指每两个字节间的间隙,比如你读取了第一个字符,而第二个字
符在收到第一个后的51ms后收到,如果你将此参数设为50,那么将触发超时。第二个参数
和第三个一般连起来用,第二个意味着总的等待时间。看一下下面这句话,
ReadFile(FileHandle,buf,toRead,actRead,nil);
//toRead=1000;
如果我们分别将上面的3个值设为50,100,80,那这个读操作将等待多少时间呢?
1,假设要读的vcd就是坏的,也就是一个字符也读不出,那实际等待的时间为
waitTime=1000*100+80=100秒多,所以为减少等待时间,缩短第二个时间是应该的,
还有一点要做的是不要尝试去一次性读取太多内容,如果你将上面的想读取得字节数
设为1M,惨么?
2.如果有返回,也就是说你想读1000个字节,可是读到500的时候出现坏区,那样的话
等待时间可能只需要再等50ms就够了。
3.不要把第二个参数和第三个参数都设为0,这样将无限等待

做好了上面的设置,剩下就简单了,只需直接简单的调用ReadFile就可以了,在经过一
定的时间后,readfile自己会返回,无需调用什么GetCommTimeOuts ,你要做的只需
判断读取得字节数到底对不对就可以了,当然还得考虑文件尾的因素。
 
感谢lvxq兄热心而详细的解答!我已经基本明白GetCommTimeOuts的用法了,昨天晚上没
睡觉就研究这个问题!然后用这个方案优化了一下我的程序,不过还是不管用。“死读”
问题是没有了,不过“超时”时长仍然比计算出来的长很多很多,比如我用
我自定义的SetTimeOutDefault()这个函数设置超时为30毫秒,但是超时还是超过5分钟
左右,还是没起作用啊,我把全部源代码、DFM帖出来,大家用Delphi编译一下一起研究
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ComCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
pb: TProgressBar;
FLabelStatus: TLabel;
lbl_Total: TLabel;
edt_IntervalTimeOut: TEdit;
UpDown1: TUpDown;
Label1: TLabel;
od1: TOpenDialog;
sd1: TSaveDialog;
Button2: TButton;
Label2: TLabel;
edt_Mul: TEdit;
UpDown2: TUpDown;
lbl_PercentBefore: TLabel;
lbl_Percent: TLabel;
lbl_Bad: TLabel;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure edt_MulKeyPress(Sender: TObject;
var Key: Char);
private
{ Private declarations }
public
{ Public declarations }
end;


var
Form1: TForm1;
bBreak: Boolean;
function ByteToM(ByteCount: integer): Real;
//得到文件大小,单位为字节,失败则返回$FFFFFFFF
function MyGetFileSize(HFile: THANDLE): DWORD;
implementation

uses Math;

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
var
fIn, fOut:THANDLE;
Buf: array [0..63] of char;
//nRead : PDWORD;
nRead, nTotalRead,nWrite: LongWord;
TimeOut: COMMTIMEOUTS;
bResult: boolean;
VCDFileSize: DWORD;
nBadBlock: integer;
//坏块个数
procedure StopReadImmediate();
begin

//completed immediately read disk!!
TimeOut.ReadIntervalTimeout:= MAXDWORD;//StrToInt(edt_IntervalTimeOut.Text);
TimeOut.ReadTotalTimeoutMultiplier:= 0;//StrToInt(edt_Mul.Text);
TimeOut.ReadTotalTimeoutConstant:= 0;
SetCommTimeouts(fIn, TimeOut);
end;

procedure SetTimeOutDefault();
begin

//Set time out
TimeOut.ReadIntervalTimeout:= 0;//StrToInt(edt_IntervalTimeOut.Text);
TimeOut.ReadTotalTimeoutMultiplier:= 0;//StrToInt(edt_Mul.Text);
TimeOut.ReadTotalTimeoutConstant:= StrToInt(edt_Mul.Text);
SetCommTimeouts(fIn, TimeOut);
end;

begin

//错误处理
try
StrToInt(edt_IntervalTimeOut.text);
StrToInt(edt_Mul.text);
except
MessageDlg('超时设置有误!',mtWarning, [mbOK], 0);
exit;
end;

//打开文件
if od1.Execute then

begin

fIn:= CreateFile(PChar(od1.FileName), GENERIC_READ ,
FILE_SHARE_READ , nil, //security
OPEN_EXISTING ,
FILE_ATTRIBUTE_ARCHIVE or
FILE_ATTRIBUTE_COMPRESSED or
FILE_ATTRIBUTE_HIDDEN or
FILE_ATTRIBUTE_NORMAL or
FILE_ATTRIBUTE_OFFLINE or
FILE_ATTRIBUTE_READONLY or
FILE_ATTRIBUTE_SYSTEM or
FILE_ATTRIBUTE_TEMPORARY,
0);
if fIn = INVALID_HANDLE_VALUE then

begin

MessageDlg('打开文件失败!',mtError, [mbOK], 0);
exit;
end;

VCDFileSize:= MyGetFileSize(fIn);
if VCDFileSize = 0 then

begin

MessageDlg('VCD文件长度为零,无法处理!',mtError, [mbOK], 0);
exit;
end;


lbl_Total.Caption:= 'VCD文件总长:' + FloatToStr(ByteToM(VCDFileSize)) + ' M';
nTotalRead:= 0;
pb.Min:= 0;
pb.Max:= Round(ByteToM(VCDFileSize));
bBreak:= False;
//Out file
sd1.DefaultExt:= od1.DefaultExt;
sd1.Filter:= od1.Filter;
if sd1.Execute then

begin

fOut:= CreateFile(PChar(sd1.FileName), GENERIC_WRITE ,
0 , nil, //security
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL , 0);
if fOut = INVALID_HANDLE_VALUE then

begin

MessageDlg('创建文件失败!',mtError, [mbOK], 0);
exit;
end;

nBadBlock:= 0;
lbl_Bad.Visible:= False;
lbl_Percent.Caption:= '0%';
lbl_Percent.Visible:= true;
lbl_PercentBefore.Visible:= true;

SetTimeOutDefault();
//begin
read vcd.
try
try
repeat
bResult:= ReadFile(fIn, Buf, SizeOf(Buf), nRead, nil);
if bBreak then
break;
if bResult and (nRead <> 0) then

WriteFile(fOut, Buf, SizeOf(Buf), nWrite, nil)
else
begin

//Stop read disk first;
StopReadImmediate();
//Restore time out settings;
SetTimeOutDefault();
inc(nBadBlock);
lbl_Bad.Caption:= '发现 '
+ IntToStr(nBadBlock) + ' 个坏块';
if not lbl_Bad.Visible then

lbl_Bad.Visible:= true;
end;

nTotalRead:= nTotalRead + nRead;
FLabelStatus.Caption:= '已经截取了:【'
+ IntToStr(nTotalRead) + '】个字节('
+ FloatToStr(nTotalRead / Power(2,20)) + 'MB)';
//Show read progress
pb.Position:= Round(ByteToM(nTotalRead));
lbl_Percent.Caption:=
IntToStr(Round(nTotalRead / VCDFileSize * 100)) + '%';

Application.ProcessMessages;
if bBreak then
break;
until bResult and (nRead = 0);
//End of file
finally
CloseHandle(fIn);
CloseHandle(fOut);
end;

MessageDlg('VCD 截取成功!',mtInformation, [mbOK], 0);
except
MessageDlg('VCD 截取失败!',mtError, [mbOK], 0);
end;

end;

//save dialog
end;

//Open dialog
end;


function ByteToM(ByteCount: integer): Real;
begin

Result:= ByteCount / Power(2, 20);
end;


function MyGetFileSize(HFile: THANDLE): DWORD;
var
dwSizeLow, dwSizeHigh, dwError: DWORD;
begin

Result:= $FFFFFFFF;
dwSizeLow := GetFileSize (hFile, @dwSizeHigh) ;
// If we failed ...
if (dwSizeLow = $FFFFFFFF) then

begin

dwError := GetLastError();
if dwError <> NO_ERROR then

begin

// Deal with that failure.
MessageDlg('读取文件大小时发生错误,错误代号为:' + IntToStr(dwError)
,mtError, [mbOK], 0);
exit;
end;

end;

// End of error handler.
Result:= dwSizeLow or (dwSizeHigh shl 32);

end;


procedure TForm1.Button2Click(Sender: TObject);
begin

bBreak:= true;
end;


procedure TForm1.edt_MulKeyPress(Sender: TObject;
var Key: Char);
begin

if not (Key in ['0'..'9']) then

Key:= #0;
end;

end.

---DFM---
object FLabelStatus: TLabel
Left = 16
Top = 80
Width = 60
Height = 12
Caption = '读取状态:'
end
object lbl_Total: TLabel
Left = 16
Top = 56
Width = 84
Height = 12
Caption = 'VCD 文件总长:'
end
object Label1: TLabel
Left = 215
Top = 45
Width = 204
Height = 12
Caption = '读两个字节之间超时 毫秒'
Visible = False
end
object Label2: TLabel
Left = 215
Top = 21
Width = 192
Height = 12
Caption = '总超时 秒'
end
object lbl_PercentBefore: TLabel
Left = 192
Top = 128
Width = 48
Height = 12
Caption = '已截取:'
Visible = False
end
object lbl_Percent: TLabel
Left = 248
Top = 128
Width = 12
Height = 12
Caption = '0%'
Visible = False
end
object lbl_Bad: TLabel
Left = 336
Top = 78
Width = 91
Height = 14
Caption = '发现 0 个坏块'
Color = clRed
Font.Charset = GB2312_CHARSET
Font.Color = clYellow
Font.Height = -14
Font.Name = '宋体'
Font.Style = []
ParentColor = False
ParentFont = False
Visible = False
end
object Button1: TButton
Left = 16
Top = 16
Width = 75
Height = 25
Caption = '截取VCD'
TabOrder = 0
OnClick = Button1Click
end
object pb: TProgressBar
Left = 16
Top = 104
Width = 441
Height = 16
Min = 0
Max = 100
TabOrder = 1
end
object edt_IntervalTimeOut: TEdit
Left = 333
Top = 40
Width = 39
Height = 20
MaxLength = 1
TabOrder = 2
Text = '5'
Visible = False
OnKeyPress = edt_MulKeyPress
end
object UpDown1: TUpDown
Left = 372
Top = 40
Width = 16
Height = 20
Associate = edt_IntervalTimeOut
Min = 0
Max = 20
Position = 5
TabOrder = 3
Thousands = False
Visible = False
Wrap = False
end
object Button2: TButton
Left = 120
Top = 16
Width = 75
Height = 25
Caption = '终止'
TabOrder = 4
OnClick = Button2Click
end
object edt_Mul: TEdit
Left = 333
Top = 18
Width = 39
Height = 20
MaxLength = 2
TabOrder = 5
Text = '30'
OnKeyPress = edt_MulKeyPress
end
object UpDown2: TUpDown
Left = 372
Top = 18
Width = 15
Height = 20
Associate = edt_Mul
Min = 0
Max = 50
Position = 30
TabOrder = 6
Thousands = False
Wrap = False
end
object od1: TOpenDialog
DefaultExt = 'dat'
Filter =
'所有VCD文件(*.dat;*.mpg;*.mpeg;*.avi;*.asf;*.rm;*.ram;*.mpa)|*.d' +
'at;*.mpg;*.mpeg;*.avi;*.asf;*.rm;*.ram;*.mpa|数字音频文件(*.dat)' +
'|*.dat|电影剪切(*.mpg;*.mpeg;*.mpa)|*.mpg;*.mpeg;*.mpa|Real 格式' +
'电影(*.rm;*.ram)|*.rm;*.ram|微软电影格式(*.asf;*.avi)|*.asf;*.av' +
'i|所有文件(*.*)|*.*'
Left = 144
Top = 64
end
object sd1: TSaveDialog
DefaultExt = 'dat'
Left = 272
Top = 64
end
 
好东西大家一起研究,gz
 
TimeOut.ReadIntervalTimeout:= 50;
TimeOut.ReadTotalTimeoutMultiplier:= 5`;
TimeOut.ReadTotalTimeoutConstant:= 100;
try this
 
还是不行啊,照样死读!
我就这样设置的:
TimeOut.ReadIntervalTimeout:= 50;
TimeOut.ReadTotalTimeoutMultiplier:= 5`;
TimeOut.ReadTotalTimeoutConstant:= 100;
看来这个函数好像解决不了死读光盘问题啊!
 
Windows里I/O的操作是最容易出错的。不过你的问题其实只要知道这个函数到底有没有用
你只要设个断点,看readfile到底花了多少时间,会不会触发超时就可以了,照你现在的
现象来看,似乎总能在过一段时间后返回,尽管这个时间可能太长,那我想,结束这个读
操作的原因不外乎两种,一,你的超时有相应了,二,windows升起了异常,而这个异常
很有可能是io异常,我想通过调试,你应该可以分析出你结束读操作的原因,再针对这个
进行处理是否会好一点?

 
Windows没有发生异常,超时函数的确起了作用,唯一地问题是超时时长根本不是我
所设置的几秒,而是几十、甚至是几分钟!如果不调用这个超时函数,直接调用Delphi
自带的FileRead函数读的话肯定就是读死了!你等100年都没有。:)
我想对CommTimeOut函数的参数还是缺乏理解吧,或者这个函数就根本无法控制光驱
文件的读写,那东东可能是不受控的。
 
多人接受答案了。
 
后退
顶部